# Warren — environment configuration. # # Casual installs never open this file. `warren up` collects the credentials # it needs interactively and stores them under ~/.warren/ (mode 0600). Read # docs/quickstart.md for that path. Real environment variables always win over # the wizard's stored file. # # This file is the operator reference. Copy it to `.env` (`cp .env.example # .env`) before `docker compose up`. Variables are warren-namespaced # (`WARREN_*`) except for cross-tool ones warren consumes verbatim # (ANTHROPIC_API_KEY, GITHUB_TOKEN). Read docs/credentials.md for the model # and GitHub credential paths in full. # ---------- first-run credentials ---------- # Operator bearer for protected API and UI actions. Under the default token # auth mode, leave this blank on first boot to let warren mint a token, persist # it under WARREN_DATA_DIR, and print it once as `mintedOperatorToken` in the # boot log. Set an explicit value when the deployment supplies its own secret; # that value always wins. Health/version probes, bootstrap registration, and # static UI assets have their documented unauthenticated posture, while # operator and mutation surfaces require this bearer. # # The operator token also seeds run-scoped callback tokens and signed preview # cookies whenever previews are enabled, in path or subdomain mode. `warren # doctor` warns when an explicit token matches a placeholder or is too short. WARREN_API_TOKEN= # At least one provider/SCM token reaches the agent so it can call its model # API and operate on the project repo. Both are optional at warren boot and # only required when a run or forge operation uses them. # # ANTHROPIC_API_KEY is forwarded into the sandbox by warren's claude-code # runtime profile, which default-allows ANTHROPIC_API_KEY, # ANTHROPIC_AUTH_TOKEN, ANTHROPIC_BASE_URL, and CLAUDE_CODE_OAUTH_TOKEN # (src/runtime/local/profile.ts). # # NOTE on cost caps (warren-f3c3): warren computes costUsd at API prices # regardless of credential, and maxCostUsd always enforces against that # number — it is a runaway brake on estimated usage, not a bill. When only # CLAUDE_CODE_OAUTH_TOKEN is set (subscription auth, no ANTHROPIC_API_KEY), # runs are stamped costBasis=subscription_estimate and the UI shows the cost # as an estimate; the cap still applies to that estimate. # # Multi-provider agents (e.g. the pi runtime) read additional provider keys # from the container env. Set whichever providers your agents use; warren's # per-runtime env allowlist forwards them into the sandbox. The supervisor # inherits container env into the warren process by default, so just # adding a variable here is enough — no docker-compose change required. # # GITHUB_TOKEN is the static-PAT forge credential (WARREN_FORGE=github). Git # network ops (reap-time `git push`, project clones, refreshes) authenticate # per-spawn via minted credentials, never via a global gitconfig rewrite. ANTHROPIC_API_KEY= GITHUB_TOKEN= # Optional provider keys for multi-provider runtimes (warren-f8c0). Warren's # runtime profiles forward these into the sandbox when set; unset keys are # silently skipped. # OPENAI_API_KEY= # GEMINI_API_KEY= # GOOGLE_API_KEY= # PI_API_KEY= # GROQ_API_KEY= # MISTRAL_API_KEY= # DEEPSEEK_API_KEY= # OPENROUTER_API_KEY= # OPENCODE_API_KEY= # # OpenCode Go (subscription) is provider id `opencode-go`. Same env var as # OpenCode Zen; dispatch Pi with provider `opencode-go`, not `openrouter`. # # The canonical provider → env-key mapping lives in src/core/providers.ts # (warren-fb8d). On the k8s topology each provider's canonical key is sourced # from a per-provider Secret (`warren--key` / `api-key`) instead of # the process env — see deploy/k8s/base/secrets.yaml. # ---------- optional: published image tag (compose only) ---------- # Which `ghcr.io/jayminwest/warren` tag docker-compose.yml pulls # (warren-26be). Compose reads this at interpolation time; warren itself # never sees it. `latest` tracks main — pin a release (`v0.10.4`) or a # moving minor (`0.10`) if you'd rather upgrade deliberately. # WARREN_IMAGE_TAG=latest # ---------- optional: auth provider (warren-851b) ---------- # Which AuthProvider protects the API. Resolved ONCE at boot; an # unrecognized value refuses the boot rather than falling back. # # token (default) every route except /healthz requires the # WARREN_API_TOKEN bearer token. Leave it unset for this. # public READ-ONLY ANONYMOUS ACCESS IS ON. A caller with no # Authorization header is admitted as an anonymous actor # holding the `readPublic` capability: it can read the public # projection of runs, projects and agents — including run # prompts and per-run cost — on EVERY project registered on # this instance. Mutations, operator-only surfaces and # diagnostics stay closed. Only turn this on for an instance # whose registered repos are all public and whose run prompts # you are happy to publish. # # WARREN_API_TOKEN is required in BOTH modes — `public` widens who may # read, it never removes the operator's way in. A token holder still # gets the full-capability operator actor, so one deployment serves both # spectators and you. `--no-auth` (loopback dev) still wins over this. # WARREN_AUTH=token # ---------- required WHEN WARREN_AUTH=public: allowlist (warren-ce9b/1841) ---------- # Comma-separated list of what this public instance may hold. Each entry is # EITHER a bare GitHub owner (my-org) or one specific repo # (some-owner/some-repo). Both shapes may appear in the same list. # Read ONLY under WARREN_AUTH=public; token mode ignores it entirely. # # This is the structural half of the public posture: the per-response # field redaction is an allowlist per body, this is the guarantee that # there is nothing private on the instance for a redaction miss to leak. # Two enforcement points: # # boot every already-registered project must match an entry, # or warren refuses to start and names the offenders. # Delete them (DELETE /projects/:id) or widen this # list, then restart. # POST /projects a non-allowlisted repo is rejected with 400 before # anything is cloned. # # Fails closed: WARREN_AUTH=public with this unset or empty REFUSES the # boot — an empty list never means "allow everything". # # CHOOSING A SHAPE. An owner entry admits EVERY repo under that owner, # including private ones and any repo created later. That is only safe for # a dedicated org whose contents are uniformly public. For a personal # account that also holds private work, list the repos explicitly — this # is the common case and the reason repo entries exist. # # Neither shape asks GitHub whether a repo is really public: that would put # a network dependency on every registration and on boot. The list is # offline and deterministic, so it is on you to only list what you would # publish. # WARREN_PUBLIC_ALLOWLIST=my-org,some-owner/some-repo # # Deprecated alias, still read (with a warning) so an older .env keeps # working. It accepts repo entries too — only the name is deprecated. # WARREN_PUBLIC_ORG_ALLOWLIST=my-org # ---------- optional: server bind ---------- # WARREN_BIND_HOST=0.0.0.0 # WARREN_BIND_PORT=8080 # # Host-vs-container distinction (docker-compose only): WARREN_BIND_PORT # moves the CONTAINER-INTERNAL bind. WARREN_HOST_PORT moves the HOST # publish — the port your machine exposes — while the container side # stays fixed at 8080. Set this when the host already has 8080 in use; # docker-compose.yml maps "${WARREN_HOST_PORT:-8080}:8080". # WARREN_HOST_PORT=8080 # WARREN_BIND_SOCKET=/run/warren/warren.sock # presence flips bind to unix # WARREN_DISABLE_UI= # "1" to skip serving src/ui/dist # WARREN_UI_DIST_DIR=/app/src/ui/dist # override UI dist path # ---------- optional: extra seed agents (warren-e376) ---------- # Path to a JSON array of AgentDefinition objects seeded into the agents # registry on every boot, alongside the built-ins (the registry is # built-ins-only since pl-3a79 retired the canopy library + # POST /agents/refresh). Idempotent and drift-aware: rows whose # frontmatter.source is "builtin" are upserted when the file changes. # Used by the acceptance harness to register its stub-shell agent. # WARREN_SEED_AGENTS_FILE=/etc/warren/seed-agents.json # ---------- optional: event-stream concurrency caps ---------- # `GET /runs/:id/events?follow=1` and `GET /plan-runs/:id/events?follow=1` # hold a connection open for the life of a run, and warren disables Bun's # per-request idle timeout so a quiet agent's stream isn't reaped. The # control plane is a single replica, so these caps are what keep the two # routes from being an unbounded connection-exhaustion vector (warren-25f6). # Over either cap the request gets an immediate 503 + `Retry-After`; # already-attached streams are untouched. Set a knob to 0 to disable it. # `warren_event_streams` on /metrics reports the live count. # # Instance-wide concurrent event streams. # WARREN_MAX_EVENT_STREAMS=200 # # Concurrent event streams per client. The client is read from # `X-Forwarded-For` counting from the RIGHT, skipping # WARREN_EVENT_STREAM_TRUSTED_PROXY_HOPS infrastructure entries, else the # socket peer address. The left-most hop is client-controlled, so keying on # it let one source rotate the header and drain the global budget. # WARREN_MAX_EVENT_STREAMS_PER_CLIENT=5 # # Wall-clock budget for one connection, as a duration ("4h", "90m"). # Defaults to 4h. Expiry ends the stream cleanly and the UI treats a clean # close as "ended" rather than reconnecting, so a client watching a run for # longer than this stops getting live updates until it reconnects. Set 0 to # disable the cap, at the cost of a wedged client holding a slot forever. # WARREN_EVENT_STREAM_MAX_LIFETIME=4h # ---------- optional: storage ---------- # Data root. The compose volume mounts here; warren.db and cloned project # repos all live under it. # WARREN_DATA_DIR=/data # Database URL (R-13 bring-your-own-database). When unset, warren falls # back to WARREN_DB_PATH; when both are unset, it defaults to a sqlite db # under WARREN_DATA_DIR. WARREN_DB_URL wins when both are set. # sqlite:///data/warren.db sqlite at /data/warren.db (default shape) # :memory: ephemeral in-memory sqlite (tests only) # postgres://user:pw@host/db postgres-backed control plane (org-scale) # WARREN_DB_URL=sqlite:///data/warren.db # WARREN_DB_POOL_MAX=10 pg pool size; sqlite ignores this. # Legacy sqlite path. Synthesized into a `sqlite://` WARREN_DB_URL when # WARREN_DB_URL is unset. Prefer WARREN_DB_URL going forward. # WARREN_DB_PATH=/data/warren.db # Clone root for project repos. Defaults to /projects, so # clones stay on the one mounted volume (warren-5c42). Only override it when # the clones belong somewhere else, and keep that path on a volume the agent # runtime can also see. # WARREN_PROJECTS_DIR=/data/projects # Test substrate (R-13 step 6). Drives `withDb()` in src/db/testing.ts. # Defaults to sqlite (today's `:memory:` path). Set both to opt into pg # unit tests; CI exposes them in the postgres matrix job. Each `withDb()` # call creates a fresh `warren_test_<8hex>` schema and drops it on close. # WARREN_TEST_DIALECT=postgres # WARREN_TEST_PG_URL=postgres://warren:warren@127.0.0.1:5432/warren_test # ---------- optional: supervisor ---------- # Override the binary the supervisor execs. Defaults match the image # layout (bun on PATH, server entry at src/server/main/index.ts). # WARREN_SUPERVISOR_BUN=bun # WARREN_SERVER_ENTRY=src/server/main.ts # ---------- optional: tooling ---------- # WARREN_GIT_BINARY=git # WARREN_CN_BINARY=cn # WARREN_LOG_LEVEL=info # ---------- optional: agent git identity (warren-fe67) ---------- # Per-deployment git identity for agent commits. When both are set, the # supervisor runs `git config --global user.name/user.email` and exports # GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL / GIT_COMMITTER_NAME / GIT_COMMITTER_EMAIL # into run processes. Unset → warren logs a warn-level fallback notice # (warren-6a28) and agent commits use the host git identity (typically # unconfigured in a fresh container). # # Recommended: create a dedicated GitHub machine account for your deployment # (GitHub's ToS permits one bot account per user) and use its noreply email so # agent commits attribute to the bot, not to your personal account. Find the # account's numeric id at https://api.github.com/users/; the # noreply format is `+@users.noreply.github.com`. Pointing # GITHUB_TOKEN at a PAT minted by the same machine account also moves PR # authorship off your personal account (repos owned by a personal account # need a classic repo-scoped PAT — fine-grained PATs cannot target another # user's repos). # WARREN_GIT_AUTHOR_NAME=your-bot-login # WARREN_GIT_AUTHOR_EMAIL=1234567+your-bot-login@users.noreply.github.com # Warren's own bookkeeping-bot identity (reap-time `chore(warren): ...` # commits — seeds state), distinct from the agent author identity above. # Defaults to `warren ` — an RFC 2606 non-routable # address that attributes to nobody. Override BOTH (both halves or nothing) # to attribute bookkeeping commits to your own bot account (warren-02cd), # e.g. an address verified on the machine account. # WARREN_BOT_NAME=your-bot-login # WARREN_BOT_EMAIL=bot@your-domain.example # ---------- optional: pull-request auto-open (warren-f6af) ---------- # After reap pushes the agent's branch, warren can open a GitHub PR for # it automatically. Default ON. Set to "false" / "0" / "no" / "off" to # disable globally. Skips silently when the run isn't successful, the # push lands no commits, the branch matches the project default, or # the forge has no credential (e.g. GITHUB_TOKEN unset under PAT mode). # WARREN_AUTO_OPEN_PR=true # Optional warren UI base URL embedded in the PR body so reviewers can # jump back to the run that produced the change. Omit to skip the link. # WARREN_BASE_URL=https://warren.example.com # ---------- optional: run-branch prefix (warren-9993) ---------- # Warren composes the run workspace branch as `/`. The # default is `warren` (warren-2de0 flipped the legacy `burrow` default; set # WARREN_RUN_BRANCH_PREFIX=burrow to roll back). # # Per-project `.warren/defaults.json.runBranchPrefix` wins over this env # value when both are set. Validation: kebab/snake-case (lowercase, # digits, dots, dashes, underscores); invalid values silently fall back to # the next precedence slot — they never block a spawn. # WARREN_RUN_BRANCH_PREFIX=warren # ---------- optional: per-run preview environments (R-19 / docs/design/preview-environments.md) ---------- # Operator's preview host suffix for subdomain mode. When set with # WARREN_PREVIEW_MODE=subdomain, warren routes # `Host: run-.` to the in-sandbox preview port. # TLS termination, wildcard DNS, and wildcard certificate provisioning stay # on the operator's edge. Default path mode does not require this value: it # derives the host from the incoming warren request and serves previews on # the dedicated preview listener. # WARREN_PREVIEW_HOST=preview.warren.example.com # Preview routing mode (warren-fcb7 / docs/design/preview-environments.md path-mode addendum). The # default `path` reuses the single hostname + cert that already serves the # warren UI; previews are at `https:///p//` and no # wildcard DNS / cert is required — the right shape for a solo-operator # single-box deploy. Set to `subdomain` for the original docs/design/preview-environments.md behavior # (`https://run-./`), which scales to many # concurrent previews per host but requires a wildcard CNAME + # DNS-01-issued wildcard cert. A per-project `.warren/preview.yaml` `mode:` # pin is overridden by this env value when both are set. # WARREN_PREVIEW_MODE=path # Dedicated path-mode preview listener port (warren-3f8a). Path-mode # previews are served from their own listener — same hostname, this port — # so the preview gets its own browser origin and agent-authored preview # code cannot read the operator token out of the warren UI's localStorage. # The warren origin answers `/p//` with a 308 to this origin. # Publish this port next to the API port (docker-compose maps # "${WARREN_HOST_PREVIEW_PORT:-8081}:8081"; a reverse proxy needs a # second forwarded port on the same hostname). # # Host-vs-container distinction (docker-compose only): # WARREN_PREVIEW_PORT moves the CONTAINER-INTERNAL preview listener. # WARREN_HOST_PREVIEW_PORT moves the HOST publish — the port your # machine exposes — while the container side stays fixed at 8081. # WARREN_HOST_PREVIEW_PORT=8081 # Default: the bind port + 1. Ignored in subdomain mode and on the # unix-socket transport (which keeps the legacy same-origin mounting and # warns at boot). # WARREN_PREVIEW_PORT=8081 # Port range the SQLite-backed allocator picks from when reap launches a # preview sidecar. In-use ports are derived from # `runs.preview_state IN ('starting','live')` so the allocator is restart- # safe; exhaustion emits a `preview_failed` event with `reason=port_exhausted` # and `warren doctor` warns when ≥80% of the range is in use. Default # `30000-31000`. # WARREN_PREVIEW_PORT_RANGE=30000-31000 # Eviction-worker tuning. The sweep combines three signals + manual teardown: # (1) idle TTL — primary kill-rule, measured from proxy-tracked # `preview_last_hit_at` (falls back to `preview_started_at` for # launched-but-never-visited previews). # (2) max_lifetime — hard ceiling from launch time, regardless of activity. # (3) global LRU cap — bounds container memory; longest-idle wins. # Per-project preview.idle_ttl / preview.max_lifetime in # `.warren/defaults.json` override the global env values at tick time. # Duration strings accept `s` / `m` / `h` (e.g. `30m`, `1h30m`). # WARREN_PREVIEW_IDLE_TTL=30m # WARREN_PREVIEW_MAX_LIFETIME=8h # WARREN_PREVIEW_MAX_LIVE=20 # WARREN_PREVIEW_EVICTION_TICK_MS=60000 # Escape hatch — disable the eviction worker entirely. Intended for # acceptance tests that drive eviction inline; do not set in production. # WARREN_PREVIEW_EVICTION_DISABLED= # Fallback GC for stranded run workspaces (warren-0a9a). Per-reap # workspace teardown covers the happy path; this periodic sweep reclaims # workspaces stranded by a warren crash mid-reap or a run force-killed # before reap ran. It only destroys workspaces whose runs are all terminal # and whose newest activity is older than the TTL. The same predicate backs # the `stale_sandbox_workspaces` check in `warren doctor` and `/readyz`. # Duration strings accept `s` / `m` / `h` (e.g. `1h`, `30m`). # WARREN_WORKSPACE_GC_TTL=1h # WARREN_WORKSPACE_GC_TICK_MS=300000 # Escape hatch — disable the GC sweep entirely (the doctor/readyz check # still reports stranded workspaces so disk leaks stay visible). # WARREN_WORKSPACE_GC_DISABLED= # Run heartbeat watchdog (warren-285d). Force-fails a `running` run that # goes silent-but-busy — e.g. a runaway quality-gate command behind a # stuck bash tool call (biome pinning cores, exitCode null, no new # events). When a run's newest event (or `started_at` fallback) is older # than this budget, the watchdog cancels the runtime workload and reaps it # `failed` (failure_reason=timed_out) so its workspace and process tree are # torn down instead of pinning CPU forever. # # On by default (warren-b2dc) with a generous built-in budget (45 min = # 2700000ms) — well above any legitimately slow-but-silent tool (cold # `bun install`, large clone) — so a fresh deploy is protected without an # explicit env var. Override the budget (milliseconds) here: # WARREN_RUN_HEARTBEAT_TIMEOUT_MS=2700000 # Opt out entirely (or pin the budget to 0): # WARREN_WATCHDOG_DISABLED=1 # Tick cadence for the watchdog sweep (default 30000 = 30s). # WARREN_WATCHDOG_TICK_MS=30000 # ---------- optional: background coordinators & model tiers ---------- # Background coordinator + model-tier knobs surfaced here for completeness. # Each defaults to a safe value (stated per var); set only to tune or disable # a specific worker. Truthy values throughout: 1 / true / yes / on # (case-insensitive). # (a) Seeds CLI binary override. The trigger scheduler shells out to the # seeds CLI (`sd`) to sync issues; the plan-run / project handlers share the # same binary and recover a missing-`sd` boot with the hint "set # WARREN_SD_BINARY (or install sd on PATH) and restart". Default resolves `sd` # from PATH. An empty string is rejected at config load (ValidationError) — # unset to fall back to the default. # WARREN_SD_BINARY=sd # (b) Plan-run coordinator merge-wait budget (warren-3937). Bounded # wall-clock budget (ms) for a PR to merge once its run has ended — guards # the parent-merge gate and the child waiting_for_merge branch against a PR # that is open-but-unmergeable (failing checks, BLOCKED merge state, stuck # auto-merge). Default 30 min = 1800000ms; set to 0 to disable the timeout # (unbounded wait). # WARREN_PLAN_RUN_MERGE_TIMEOUT_MS=1800000 # (c) Pause/respawn detector. The pause detector is OFF by default; set to 1 # to opt in (it watches for runs that pause abnormally). Polls every # WARREN_PAUSE_DETECTOR_TICK_MS (default 15s). # WARREN_PAUSE_DETECTOR_ENABLED=1 # (d) Model-tier overrides for the built-in agents (healer, pi, pr-fixer). # The literal model identifiers live only in src/registry/builtins/model-tiers.ts, # so bumping a model is a single edit # or a deploy-time env override with no image rebuild. Resolution freezes # onto each agent row at seed time (seedBuiltinAgents), so set these before # warren boots. Defaults: sonnet = claude-sonnet-4-6 / anthropic, # opus = claude-opus-4-8 / anthropic. # WARREN_MODEL_SONNET=claude-sonnet-4-6 # WARREN_MODEL_SONNET_PROVIDER=anthropic # WARREN_MODEL_OPUS=claude-opus-4-8 # WARREN_MODEL_OPUS_PROVIDER=anthropic