Cat Factory
Home
Get Started
GitHub
Home
Get Started
GitHub
  • Start

    • Introduction
    • Core Concepts
    • Quick Start
    • Tutorial: Your First Task to a Merged Pull Request
  • Guides

    • Recipes

      • Cookbook
    • Plan the work

      • Design Your Board
      • Clarify Requirements
      • Author a Document
      • Plan an Initiative
    • Run pipelines

      • Choose and Edit a Pipeline
      • Run a Pipeline
      • Schedule Recurring Work
      • Review and Merge Pull Requests
      • Control Spend with Budgets
    • Connect

      • Connect a Repository
      • Connect Issue & Document Sources
      • Feed Design Context to Agents
      • Preview and Test a Frontend
    • Models & prompts

      • Connect a Model Provider
      • Apply Standards with Prompt Fragments
      • Run a Claude Skill as a Step
      • Compare Prompts and Models in the Sandbox
    • Collaborate

      • Invite and Manage Your Team
      • Share Services Across Workspaces
      • Register Foundational Services
  • Deploy

    • Run Locally
    • Deploy to Node.js
    • Deploy to Cloudflare
    • Deploy on Kubernetes
    • Lay Out a Kubernetes Cluster
    • Set Up a Local Kubernetes Cluster on Windows
    • Register the GitHub App
    • Set Up Enterprise SSO
    • Set Up Your Deployment Repository
    • Configuration
  • Operate

    • Observability
    • Set Up Notifications
    • Run Jobs on Your Own Runners
    • Provision Ephemeral Environments
    • Debug a Run from Outside the Browser
    • Troubleshooting
    • Upgrades & Data Retention
  • Extend

    • Add a Custom Agent Kind
    • Add a Custom Gate or Judge
    • Add a Custom Provider
    • Extend the App with Frontend Modules
    • Integration Manifests
    • Give Agents External Tools (MCP)
    • Package a Reusable Operation
    • Register an Initiative Preset
    • Public API
    • Official SDKs
    • MCP Server
    • Cloudflare OS Gatekeeper
  • Reference

    • Architecture
    • Agent Isolation Model
    • Security Model & Hardening
    • Packages & Repository Layout
    • GitHub and GitLab Support Matrix
    • Environment Variables
    • API Endpoint Reference
    • Glossary

Environment Variables

Every environment variable the Cat Factory backends read, grouped by purpose and annotated with the deployment modes each applies to. This page is generated from the canonical list in the code repository, which a CI guard reads on every change, so the two cannot drift.

That canonical list is the backend's own, and a deployment needs a few things it does not carry — frontend build variables, and integration credentials that are entered in the UI rather than read from the environment. For the narrative on how to configure a deployment (what to set first, what refuses to boot without it), read Configuration, which is the authoritative page for standing a deployment up.

These names are RESERVED

Every variable in this reference belongs to the platform, and none of them can be the KEY a capability credential is looked up by. A tool server (MCP) or a generative binary integration declares the credential it needs by name, and the default resolver reads that name off this same environment before the value is injected into an agent process: a declaration of ENCRYPTION_KEY would hand a prompt-injectable agent the key every stored credential is sealed with. Such a declaration is refused at boot and again at dispatch. Look the credential up under a name of the integration's own (ACME_IMAGE_API_KEY) and set it beside these.

The rule is enforced by isReservedPlatformEnvKey (backend/packages/contracts/src/reserved-env-keys.ts), which reserves the platform's prefix families (AUTH_, GITHUB_, LOCAL_, …) plus the remaining exact names, case-insensitively, because process.env lookup is case-insensitive on Windows. scripts/check-reserved-env-keys.mjs fails CI when a variable documented below is not covered, so adding a row here is also how the reserved set stays current.

The model-provider keys are reserved too. That looks like over-reach and is not: OPENAI_API_KEY is billable and exfiltratable, and an integration that wants to call OpenAI on the deployment's account should say so in its own variable rather than silently inherit the one the model router spends.

A credential's OTHER name is not reserved

The floor above binds the LOOKUP name, because that is the one that can read this environment. It does not bind the variable a resolved value is INJECTED under in the agent's or the MCP server's process, which reads nothing at all. A declaration keeps them apart with envName:

secretKeys: [{ key: 'ACME_GITHUB_TOKEN', envName: 'GITHUB_PERSONAL_ACCESS_TOKEN' }]

That escape is why the prefix families can be as broad as they are. The GitHub MCP server's client reads GITHUB_PERSONAL_ACCESS_TOKEN, the Slack one reads SLACK_BOT_TOKEN, and an AWS one reads AWS_ACCESS_KEY_ID: the platform reads none of those, but each falls inside a family it does own, and no deployment can rename what a vendor's own SDK looks for. Injection names have their own, narrower rule instead (isToolchainEnvName): not PATH, NODE_OPTIONS, npm_config_* or the other names that would reconfigure the process rather than authenticate a call.

Name a capability credential under a prefix of your own

The lookup name is yours to pick, so pick a family and stay in it. The convention this repo's own registrations follow is MCP_… for a tool server's credentials and GEN_… for a generative binary integration's, or a house prefix (ACME_…) where one deployment owns both. Two reasons, and the second is the one that bites:

  • Every family the platform uses is reserved (AUTH_, GITHUB_, LOCAL_, SLACK_, the model-provider keys, …), so a name that reads naturally is quite often refused. MCP_SLACK_TOKEN is not, and envName puts the value into the variable the vendor's own client insists on.
  • allowKeys is set PER DEPLOYMENT, not per capability, and it gates every subject the resolver serves. An allow-list holding only MCP_… silently resolves nothing for a registered image or music generator, and the failure surfaces as the agent reporting that integration unavailable with nothing pointing back here. List a prefix per family, or the exact keys your registrations declare.

Whether a name resolves at all is answerable without starting a run: Infrastructure → Capability credentials lists every key the deployment's capabilities declare, and the tool-server rows above it carry a Test button that resolves the credential through the real chain and speaks MCP to a remote server. See custom-agents.md.

The environment is the FALLBACK, not the primary home

Capability credentials are resolved from the per-workspace capability-credential store first, falling back to this environment per key, so a multi-tenant deployment gives each workspace its own vendor account instead of sharing one variable. See ADR 0041. Setting a variable still works and is the right mechanism for a single-tenant or local install.

Deployment modes

The same @cat-factory/server app ships to several targets. "Mode" is which facade boots plus a few switches:

ModeFacadeMeaning
Cloudflareruntimes/cloudflare (@cat-factory/worker)The Worker: D1 + Durable Objects + Workflows + Containers. Config comes from wrangler.toml [vars] + secrets + bindings.
Noderuntimes/node (@cat-factory/node-server)The hosted Node service: Postgres (Drizzle) + pg-boss. Config comes from process.env. Also called "remote node".
Localruntimes/local (@cat-factory/local-server)The Node facade a single developer runs on their machine: per-run local containers + a GitHub PAT. Reuses every Node variable plus the LOCAL_* extras and some local-friendly defaults.
Mothershipa Node/Cloudflare deployment acting as the hosted org backend, plus a local laptop that delegates persistence to it over RPCThe hosted side reads the Node/Cloudflare variables; the laptop reads the Local variables plus LOCAL_MOTHERSHIP_*.

In the tables below the Modes column uses: CF (Cloudflare), Node, Local, MS (mothership-specific). A variable marked Node is also read by Local and by a mothership-mode laptop, because Local reuses the Node config loader; the tables call out Local/MS only when a variable is exclusive to those modes.

Spend budgets

Budgets are tiered: a per-workspace monthly limit (configured in the UI), a per-account limit, and a per-user limit. The two variables below are operator hard ceilings on the account and user tiers. When set, a UI user cannot configure a value above the cap (it is also enforced server-side), the cap is shown on the budget configuration screen, and it acts as the effective tier limit when nothing is configured. How budgets behave for a user is on the website: Budgets. Amounts are in the base pricing currency (EUR by default).

VariableModesDefaultDescription
BUDGET_MAX_MONTHLY_PER_ACCOUNTCF, Node, MSnone (uncapped)Hard ceiling on the account-tier monthly budget any account may configure.
BUDGET_MAX_MONTHLY_PER_USERCF, Node, MSnone (uncapped)Hard ceiling on the user-tier monthly budget any user may configure.

Notes: these are read by the Node and Cloudflare config loaders, so they apply in the Node, Cloudflare, and mothership-hosted deployments. A single-user local deployment reads them too (Local reuses the Node loader) but they are rarely meaningful there. The per-workspace budget itself is not an env variable: it is configured per workspace in the UI (Workspace settings -> Budget) and defaults to about 100 EUR/month.

Core service & networking

VariableModesDefaultDescription
DATABASE_URLNode, Localrequired (Node)Postgres connection string. Prefer 127.0.0.1 over localhost for a local DB: on Windows + Docker Desktop localhost resolves to IPv6 ::1 first and the connection RESETS at boot (ECONNRESET).
DB_SCHEMANodepublicSchema for the app's unqualified tables (relocated via the connection search_path); set when sharing a Postgres with other services. Plain lowercase identifier.
DB_MIGRATIONS_SCHEMANodedrizzleSchema for the Drizzle migration ledger, so it can't collide with another Drizzle service's drizzle.__drizzle_migrations. Plain lowercase identifier.
DB_PGBOSS_SCHEMANodepgbossSchema for pg-boss's durable-job queue tables. Plain lowercase identifier.
PORTNode, Local8080HTTP listen port.
HOSTNode, Localall interfacesBind address.
PUBLIC_URL / WORKER_PUBLIC_URL / APP_BASE_URLNode / CFderivedPublic base URL used to build callback/redirect URLs.
CORS_ALLOWED_ORIGINSCF, NodenoneComma-separated allowed CORS origins.
ENVIRONMENTCF, NodedevelopmentDeployment environment label (production, local, ...).

Realtime (Node horizontal scaling)

VariableModesDefaultDescription
REDIS_URLNodenone (single node)Enables the Redis pub/sub cross-node WebSocket propagator. ioredis is imported only when set.
REDIS_REALTIME_CHANNELNodedefault channelRedis channel for realtime fan-out.
REALTIME_NODE_IDNodegeneratedStable id for this replica in the propagator.

Authentication

VariableModesDefaultDescription
AUTH_SESSION_SECRETCF, Noderequired (Node/Local)HMAC secret for session tokens (>= 32 chars).
HARNESS_SHARED_SECRETCF, Noderequired (executor)Shared secret the orchestrator sends on every agent-container harness call (x-harness-secret) so a job container only trusts this service (>= 16 chars, stable across restarts).
AUTH_SESSION_TTL_HOURSNodedefault TTLSession lifetime.
AUTH_DEV_OPENNode, Localfalse (Local true)Dev-open auth (no sign-in).
AUTH_PASSWORD_ENABLEDNode, Localfalse (Local true)Enable password auth.
AUTH_OPEN_SIGNUPLocaltrue (Local)Allow open sign-up.
AUTH_ALLOWED_LOGINS / AUTH_ALLOWED_ORGS / AUTH_ALLOWED_EMAIL_DOMAINSCF, NodenoneAllow-lists gating who may sign in.
AUTH_ALLOWED_REDIRECT_ORIGINS / AUTH_SUCCESS_REDIRECT_URL / AUTH_CALLBACK_URLCF, NodenoneOAuth redirect configuration.
AUTH_MACHINE_TOKEN_TTL_MSCF, Node30 daysLifetime of a machine token minted for a mothership-mode node.
AUTH_TRUST_PROXYNode, LocalfalseLet the password throttle read the client address from x-forwarded-for instead of the socket peer. Set ONLY when a proxy you control terminates every request: the header is attacker-supplied otherwise, and a client-chosen address defeats the throttle. cf-connecting-ip is deliberately NOT consulted on Node (a generic reverse proxy forwards it untouched); the Worker reads that header alone, because its edge injects and overwrites it.
AUTH_TRUST_PROXY_HOPSNode, Local1How many trusted proxies sit in front of this process, used to pick the client hop out of an x-forwarded-for chain. The rightmost entry is the one the nearest proxy appended, so one proxy needs no change; a CDN plus a load balancer is 2. A chain shorter than this is discarded in favour of the socket peer.
GITHUB_OAUTH_CLIENT_ID / GITHUB_OAUTH_CLIENT_SECRETCF, Nodenone"Login with GitHub" OAuth app.
GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_CLIENT_SECRET / GOOGLE_OAUTH_REDIRECT_URLNodenone"Login with Google" OAuth app.
AUTH_SSO_ISSUER_URL / AUTH_SSO_CLIENT_ID / AUTH_SSO_CLIENT_SECRETCF, NodenoneEnterprise SSO through the deployment's own OpenID Connect provider (Okta, Entra ID, Auth0, Keycloak, PingFederate, a Shibboleth OP …). ONE generic adapter: the issuer's discovery document supplies every endpoint. All three are required together: a partial set REFUSES to boot, as does a non-https issuer, a weak AUTH_SESSION_SECRET, or AUTH_DEV_OPEN alongside SSO. See auth.md.
AUTH_SSO_LABEL / AUTH_SSO_SCOPES / AUTH_SSO_REDIRECT_URLCF, Nodesee auth.mdSSO presentation + request shaping: the sign-in button label, the space-separated scopes (openid is added when absent), and an explicit redirect_uri for a deployment whose public URL differs from the request origin.
AUTH_SSO_GROUPS_CLAIM / AUTH_SSO_REQUIRED_GROUPS / AUTH_SSO_ALLOWED_EMAIL_DOMAINSCF, Nodegroups / none / noneOptional narrowings on SSO admission. By default the IdP's own app assignment is the whole allowlist (which is the point of SSO); these restrict it further to named directory groups and/or verified email domains, checked on EVERY sign-in.

VCS integration (GitHub / GitLab)

VariableModesDefaultDescription
GITHUB_APP_ID / GITHUB_APP_PRIVATE_KEYCF, NodenoneThe GitHub App (installation-based repo access + CI/merge gates).
GITHUB_APP_SLUG / GITHUB_API_BASE / GITHUB_SETUP_REDIRECT_URLCF, NodedefaultsGitHub App metadata + API base (GitHub Enterprise). The API base also decides the WEB host the SPA links repositories, pull requests and issues to (/api/v3 stripped, api.github.com mapped to github.com); a base with neither shape names no host and those links are withheld.
GITHUB_PATLocal, MSnonePersonal access token local mode uses instead of a GitHub App (push token + CI/merge client). OPTIONAL: without it a developer can sign in with a token on the sign-in screen and it becomes the deployment's credential (sealed on the machine, no restart). Setting it here WINS over an installed one, and closes that browser flow. In mothership mode, neither ⇒ GitHub runs on installation tokens the mothership's GitHub App mints over the machine API.
GITLAB_PAT / GITLAB_API_BASELocalnoneGitLab personal access token + API base for a GitLab local deployment. The API base also decides the WEB host the SPA links projects, merge requests and issues to (/api/v4 stripped); a base with no such suffix names no host and those links are withheld.
LOCAL_VCS_CREDENTIAL_DBLocal, MS~/.cat-factory/vcs-credential.sqliteWhere the sign-in-screen-installed source-control token is sealed. Only consulted when neither PAT above is set.

Model providers

VariableModesDefaultDescription
OPENAI_API_KEY / ANTHROPIC_API_KEY / QWEN_API_KEY / DEEPSEEK_API_KEY / MOONSHOT_API_KEYCF, NodenoneDirect vendor API keys.
OPENROUTER_BASE_URLCF, Nodepublic gatewayOpenRouter gateway base URL.
LITELLM_BASE_URLCF, Noderequired to enableOperator-hosted LiteLLM gateway (no public default).
CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_TOKEN / CLOUDFLARE_AI_GATEWAYCF, NodenoneCloudflare Workers AI over REST (Node) + AI Gateway.
BEDROCK_REGION / AWS_* / BEDROCK_MODELSCF, NodenoneOpt-in AWS Bedrock. BEDROCK_MODELS is both the resolver's allow-list and the per-model picker enablement (unset ⇒ routing-default only).
AGENT_DEFAULT_PROVIDER / AGENT_DEFAULT_MODEL / AGENT_DEFAULT_TEMPERATURE / AGENT_MAX_OUTPUT_TOKENS / AGENT_MODELSCF, Nodebuilt-in routingDefault agent routing + per-kind model overrides.

Web search

VariableModesDefaultDescription
WEB_SEARCH_SEARXNG_URL / WEB_SEARCH_SEARXNG_API_KEYCF, Node, LocalnoneSearXNG upstream.
WEB_SEARCH_BRAVE_API_KEYCF, NodenoneBrave search upstream (wins when set).
INLINE_WEB_SEARCH_ENABLED / INLINE_WEB_SEARCH_KINDS / INLINE_WEB_SEARCH_MAX_USESNodeoffInline web-search tool for non-container agents.
LOCAL_WEB_SEARCHLocalonSet off to disable the local SearXNG default.

Execution tuning

VariableModesDefaultDescription
DECISION_TIMEOUTCF, NodedefaultHuman-decision wait timeout.
JOB_POLL_INTERVAL / JOB_MAX_POLLS / JOB_POLL_FAILURE_TOLERANCENodedefaultsContainer job polling cadence + limits.
CI_POLL_INTERVAL / CI_MAX_POLLSNodedefaultsCI gate polling cadence + limits.
ADVANCE_TIMEOUTCF, Node30 minutesHang ceiling on one pipeline-step advance or status read.
CONTAINER_MAX_AGE_MINUTESNode90Max age of a per-run container before eviction.
EXECUTION_CONCURRENCY / EXECUTION_HEARTBEAT_SECONDS / EXECUTION_MAX_DRIVE_STEPS / EXECUTION_DRIVE_EXPIRE_MINUTESNodedefaultspg-boss execution worker tuning.
STALE_RUN_SWEEP_MINUTES / STALE_RUN_LEASE_MINUTESNodedefaultsStale-run sweeper cadence + lease.

Storage & retention

VariableModesDefaultDescription
ENCRYPTION_KEYCF, Node, Localrequired for sealed storesBase64 system key (>= 32 bytes) for sealed credentials.
TOKEN_USAGE_RETENTION_DAYSCF, Node395Retention for the token_usage ledger.
LLM_CALL_METRICS_RETENTION_DAYSCF, Node14Retention for the LLM-call telemetry store. Long enough that a post-mortem started days after the run can still read the calls; lower it to shrink the store's footprint.
GITHUB_RATE_LIMIT_RETENTION_DAYSCF, Node7Retention for GitHub rate-limit rows.
GITHUB_COMMIT_RETENTION_DAYSCF, Node90Retention for commit-projection rows.
GATE_OUTCOME_RETENTION_DAYSCF, Node90Retention for the settled-gate projection behind the dashboard's gate attempt statistics.
PLATFORM_RUN_DAY_RETENTION_DAYSCF, Node400Retention for the daily run rollup behind the dashboard's 30d/90d windows.
AUDIT_EVENT_RETENTION_DAYSCF, Node730Retention for the account audit log. The longest window here by design: it answers a compliance question, not an operational one. 0 disables the prune entirely.
NOTIFICATION_RETENTION_DAYSCF, Node90Retention for RESOLVED (acted or dismissed) notifications. Open cards are the actionable inbox and are never pruned.
PROVISIONING_LOG_RETENTION_DAYSCF, Node14Retention for the infrastructure provisioning event log (high churn).
LLM_RECORD_PROMPTSCF, NodefalseDeployment switch that (with the per-workspace toggle) enables storing prompts/agent context.
LOG_LEVELCF, Node, LocalinfoEmit threshold for the structured logger: debug/info/warn/error. An unrecognised value falls back to info. See backend/docs/logging.md.

Integrations & observability

The *_ALLOW_URL_HOSTS variables below are all read by one SSRF guard, so their entry format is shared: a comma-separated list whose entries match the URL host case-insensitively, either exactly (envs.corp, 10.1.2.3) or as a dot suffix when the entry begins with . (.internal matches internal and a.b.internal). There is no glob syntax, so an entry like *.internal is not a wildcard and matches nothing. Each integration resolves its own list: a host allowed to one is not thereby allowed to another.

VariableModesDefaultDescription
SLACK_ENABLEDCF, NodefalseEnable the Slack notification channel.
OBSERVABILITY_ENABLEDCF, NodefalseEnable the post-release-health observability providers.
CONSENSUS_ENABLEDNodefalseEnable the consensus-orchestration mechanism.
ENVIRONMENTS_ALLOW_HTTP_URLS / ENVIRONMENTS_ALLOW_URL_HOSTSNode, LocaloffRelax environment URL restrictions (local defaults on).
LOCAL_MODELS_ALLOW_LANCF, Node, LocalfalsePermit private-LAN hosts (RFC1918 / ULA / mDNS .local) for user-registered locally-run model endpoints, beside the always-allowed loopback. Off by default because the endpoint URL is fetched server-side, so on a shared deployment LAN reach is an internal-network SSRF grant. Local mode defaults it on (single tenant).
RUNNERS_ENABLEDCF, NodefalseEnable self-hosted runner pools.
MCP_OAUTH_REDIRECT_URLCF, Node, LocalnoneThis deployment's public app URL followed by /mcp-oauth-callback, where a vendor's authorization server redirects an operator's browser after they connect an OAuth-protected remote MCP tool server, and the same string registered as the OAuth client's redirect URI at the vendor. It points at the SPA rather than the backend: that page re-presents the vendor's code and state over the authenticated API, which is what lets the completion be session-gated at all (a vendor's redirect carries no bearer token). Operator-set rather than derived from the request, because a Host-derived value differs behind every proxy, preview URL and private hostname a deployment sits behind and the exchange then fails at the vendor with redirect_uri_mismatch. Unset ⇒ the interactive grant refuses with a 503 naming this variable; the client_credentials grant needs no redirect and works without it. Grants are sealed, so ENCRYPTION_KEY is required either way. See backend/docs/mcp-tool-servers.md.
NOTIFICATION_WEBHOOK_ALLOW_HTTP_URLS / NOTIFICATION_WEBHOOK_ALLOW_URL_HOSTSCF, Node, LocaloffRelax the strict public-https guard on a workspace's outbound notification-webhook endpoint (e.g. a receiver on an internal host, or a developer's localhost). Scoped to webhooks alone: this is the one integration whose target URL a workspace chooses, so it never rides the operator-set runner/environment allow-lists. The webhook feature itself needs no flag: it assembles wherever ENCRYPTION_KEY is set.
LANGFUSE_*CF, NodenoneLangfuse trace sink credentials.
OTEL_ENABLED / OTEL_EXPORTER_OTLP_* / OTEL_SERVICE_NAMECF, NodefalseOpenTelemetry OTLP trace + metrics exporter.
OTEL_PLATFORM_METRICS (+ _WINDOW, _INTERVAL_MS)CF, NodefalsePush per-account platform-health aggregates as OTLP gauge metrics (opt-in on top of OTEL_ENABLED).
OTEL_LOGSCF, Node, LocalfalseExport the platform's own structured log lines to the OTLP endpoint as log records (opt-in on top of OTEL_ENABLED). LOG_LEVEL governs what is exported, exactly as it governs what is written locally.
OTEL_LOGS_MAX_BATCH_SIZECF, Node, Local128Lines per OTLP log POST; also bounds the exporter's in-memory buffer (8 batches), beyond which the oldest lines are dropped and the drop count is reported on the next batch.
OTEL_LOGS_FLUSH_INTERVAL_MSNode, Local5000Node flush cadence (the Worker flushes at the end of every invocation, and a workflow wake also flushes at each durable suspension, since a per-isolate buffer has no later tick guaranteed to reach it).
PLATFORM_ALERTSCF, NodefalseEnable platform-health threshold alerting: a periodic sweep raises a platform_health notification (in-app + Slack) when the deployment's own run health crosses a threshold, auto-clearing on recovery.
PLATFORM_ALERTS_WINDOWCF, Node1hWindow each evaluation aggregates over (1h/24h/7d).
PLATFORM_ALERTS_INTERVAL_MSNode300000Node sweep interval (the Worker is cron-driven).
PLATFORM_ALERTS_MIN_RUNSCF, Node5Minimum terminal runs in the window before the failure-rate alert can fire.
PLATFORM_ALERTS_MAX_FAILURE_RATECF, Node0.5Failure rate (0..1) at or above which the failure-rate alert fires.
PLATFORM_ALERTS_MAX_P99_MINUTESCF, Node60p99 run duration (minutes) at or above which the slow-run alert fires.
PLATFORM_ALERTS_MAX_BACKLOGCF, Node50Live running/blocked/paused/pending depth at or above which the backlog alert fires.
PLATFORM_ALERTS_STALLED_BUCKETSCF, Node3Trailing trend buckets that must be completely empty before the zero-throughput alert fires. In buckets, not hours, so it scales with the window. Every other condition divides by runs and goes silent at zero, which is what made a dead deployment read as a quiet healthy one.
PLATFORM_ALERTS_MIN_STALLED_PRIOR_RUNSCF, Node5Runs the EARLIER part of the same window must have created before a silence counts as a stall, so a genuinely idle deployment stays quiet instead of paging every night. 0 alerts on silence unconditionally.
PLATFORM_ALERTS_MAX_FAILURE_KIND_SHARECF, Node0.8Share (0..1) of the window's failures ONE kind must account for before the dominant-failure alert fires. 100% evicted and 100% agent produce an identical failure rate and need opposite fixes.
PLATFORM_ALERTS_MAX_SWEEP_FAILURESCF, Node3Consecutive failed passes of one background sweeper before the sweep-degraded alert fires. A wedged sweeper makes every other signal stale without making any of them fire.
PLATFORM_ALERTS_FAILURE_KIND_RATESCF, Node(none)Per-failure-kind alert rules: kind=share[:minCount], comma-separated (e.g. evicted=0.05:3,timeout=0.2). The dominant-kind ceiling above asks whether one cause is swamping the rest; these ask whether a NAMED cause reached what this deployment tolerates from it (the share is the trigger point, fired at or above it), which no single ceiling can express (5% evictions is the substrate failing, 40% rejected is the product working). minCount is the per-rule minimum number of failures of that kind, defaulting to 1: without it a low share is a hair trigger, since five terminal runs with one eviction is already 20%. A rule that cannot be read is reported and dropped on its own, never clamped into range. A rule naming a kind this build does not produce is reported and KEPT, since a typo and a retired kind are the same string here and only a human can tell them apart. Unset means no per-kind rules.
INFRA_REACHABILITY_WATCHCF, NodefalseEnable the infrastructure-reachability watcher: a periodic sweep probes each workspace's CONFIGURED infrastructure connections (ephemeral-environment provider, self-hosted runner pool) and reports a dead one as unreachable; an infra_unreachable notification (in-app + Slack) plus a live infraSetup push that raises the setup banner. Off by default: it is the one sweep making an OUTBOUND call per workspace per pass.
INFRA_REACHABILITY_INTERVAL_MSCF, Node300000Sweep interval, floored at 30s. Honoured on both facades: Node times it directly, and the Worker (whose cron ticks every 2 min) runs the sweep only on the tick that opens a new interval window, so the cadence of the one sweep that calls OUT per workspace is the operator's to set on Cloudflare too.
INFRA_REACHABILITY_PROBE_TIMEOUT_MSCF, Node5000Per-probe timeout, clamped to 1s..60s. A probe that does not answer inside the budget counts as unreachable.
EMAIL_SYSTEM_PROVIDER / EMAIL_SYSTEM_FROM / EMAIL_SYSTEM_API_KEYNodenoneSystem email sender.
DOC_SOURCE_<SOURCE>_<FIELD>CF, Node, LocalnoneThe DEPLOYMENT's own credentials for a document source, which is what lets a code-registered prompt fragment name a LIVING document (documentRef) instead of a frozen body. <SOURCE> is the source id (CONFLUENCE, NOTION, LINEAR, FIGMA, ZEPLIN) and <FIELD> is that provider's own credential field in SCREAMING_SNAKE, so Confluence reads DOC_SOURCE_CONFLUENCE_BASE_URL / _ACCOUNT_EMAIL / _API_TOKEN and Notion reads DOC_SOURCE_NOTION_API_TOKEN. Distinct from a tenant's connected source: these authenticate reads made on the whole deployment's behalf, cached once deployment-wide. github is NOT configurable this way (its credential is a workspace's App installation). Setting some but not all of a source's variables is REPORTED at boot and leaves the source unconfigured, which then makes any fragment naming it fail validation. See reusable-operations.md.

Local mode

VariableModesDefaultDescription
LOCAL_HARNESS_IMAGELocalrecommended pinThe executor-harness image local mode pulls + runs.
LOCAL_HARNESS_IMAGE_REFRESHLocaloffRe-pull the harness image at boot.
LOCAL_CONTAINER_RUNTIMELocaldockerContainer runtime adapter (docker/podman/orbstack/colima/apple).
LOCAL_DOCKER_BINARY / LOCAL_DOCKER_NETWORK / LOCAL_HARNESS_HOST_ALIAS / LOCAL_DOCKER_ADD_HOST_GATEWAY / LOCAL_DOCKER_PRIVILEGED_TEST_JOBSLocaldefaultsDocker-CLI adapter tuning.
LOCAL_NATIVE_AGENTS / LOCAL_HARNESS_ENTRYLocaloffRun CONTAINER agents natively (no container) on the developer's claude/codex CLI.
LOCAL_NATIVE_INLINELocalon (both)Which subscription harnesses (claude-code/codex) may serve INLINE steps (reviewer/brainstorm/estimator) via the local CLI; off to disable.
LOCAL_INLINE_CLI_IDLE_TIMEOUT_MSLocal300000Kill an inline host-CLI run after this long with NO output. Bounds how long it may be STUCK, not how long it may work (every chunk re-arms it). Whole ms, 1..2147483647; anything else is reported at boot and ignored.
LOCAL_INLINE_CLI_MAX_TIMEOUT_MSLocal3600000Absolute wall-clock ceiling for one inline host-CLI run, however busy it looks: the backstop for a run that narrates forever. Same bounds; a ceiling below the idle window is reported, since it makes that watchdog unreachable.
LOCAL_HARNESS_ENV_ALLOWLocalnoneExtra variable names (comma-separated) the sanitized native-harness child env may inherit on top of the built-in allow-list.
LOCAL_HARNESS_NODE_ARGSLocalnoneExtra node arguments for the spawned native harness process.

Mothership mode

VariableModesDefaultDescription
LOCAL_MOTHERSHIP_URLMSnone (off)Enables mothership mode: the local node delegates persistence to this hosted backend.
LOCAL_MOTHERSHIP_TOKENMSminted via loginHeadless/CI machine-token override.
LOCAL_MOTHERSHIP_TOKEN_DB / LOCAL_MOTHERSHIP_CREDENTIAL_DB / LOCAL_MOTHERSHIP_SETTINGS_DB / LOCAL_MOTHERSHIP_TELEMETRY_DB / LOCAL_MOTHERSHIP_WORK_DBMSdefault pathsOverride paths for the laptop's local node:sqlite stores.

Next: Configuration for what to set first, or Upgrades & Data Retention for the retention windows in context.

Last Updated: 8/11/26, 9:33 AM
Prev
GitHub and GitLab Support Matrix
Next
API Endpoint Reference