A tall wooden shelf on the stone wall of the Plan Forge shop holding a tidy rack of small labeled glass apothecary bottles, each containing a different colored softly glowing liquid (amber, emerald, sapphire, ruby, silver) representing environment variables, a smith with a clipboard checking labels
Appendix U

Environment Variables Reference

Every environment variable Plan Forge reads, grouped by subsystem, with type, default, scope, and security note. The companion reference to Appendix T — .forge.json: settings that change per-machine or contain secrets live here; settings that travel with the project live there.

Secrets belong in .forge/secrets.json or your shell environment: never in .forge.json. The secrets file is gitignored by default; the env-var fallback works the same way in CI runners and on developer machines. The Provider API Keys table flags every secret with a lock icon.

Orientation

Plan Forge reads roughly 40 environment variables across nine subsystems. Most have sensible defaults; the only ones you typically set yourself are provider API keys (so the orchestrator can call a model) and server ports (if 3100/3101 conflict with something else on your machine).

GroupWhen you touch it
Provider API keysAlways, at least one model provider must be configured.
Azure OpenAIOnly when routing through Azure OpenAI instead of the model vendor's public API.
Server ports and networkOnly if the default ports collide or you need to harden the bridge.
Project and runtimeMostly internal (set by tests or by pforge itself).
Orchestrator timingTuning gate or worker timeouts on slow CI runners.
Feature togglesEnabling experimental subsystems or bypassing checks.
Telemetry (OTel)Sending traces to a collector.
Host detection (read-only)Never, Plan Forge reads these from your IDE to pick the right adapter.
CLI internalSet transiently by pforge itself; documented for transparency only.

Every field row uses the same six columns, Variable, Type, Default, Scope, Set when, Security, so the table reads the same way no matter which subsystem you land on. Scope is one of per-user (export in your shell profile), per-machine (system env or CI variable), or per-session (transient, set on a single invocation).

Provider API keys

The orchestrator needs at least one of these to route a slice through a non-Copilot model. All are read from the environment first, then from .forge/secrets.json as a fallback (see pforge-mcp/secrets.mjs for the loader). The dashboard Config → Secrets tab is the friendliest way to set them, it writes the secrets file atomically and never echoes the value back.

VariableTypeDefaultScopeSet whenSecurity
XAI_API_KEY 🔒string(none)per-userYou want to route slices through Grok models (grok-4.20, grok-4, grok-3, grok-3-mini).Secret. Prefer .forge/secrets.json.
OPENAI_API_KEY 🔒string(none)per-userYou want GPT models or DALL-E image generation (forge_generate_image).Secret. Prefer .forge/secrets.json.
ANTHROPIC_API_KEY 🔒string(none)per-userYou want Claude models directly (not via GitHub Copilot).Secret. Prefer .forge/secrets.json.
OPENCLAW_API_KEY 🔒string(none)per-useropenclaw.endpoint is set in .forge.json and PreAgentHandoff should authenticate.Secret. Prefer .forge/secrets.json.
GITHUB_TOKEN 🔒string(none)per-userforge_meta_bug_file, forge_classifier_issue, or forge_github_metrics needs to call the GitHub API. gh auth status is the easier path when the GitHub CLI is installed.Secret. Use a fine-scoped token; repo + issues is enough.
OPENBRAIN_KEY 🔒string(none)per-userOpenBrain replay needs authenticated access (rare; OpenBrain is local-first).Secret. Prefer .forge/secrets.json.

Azure OpenAI (alternative routing)

Set this group when your organization routes model calls through Azure OpenAI for billing, residency, or governance reasons. The keys are read by cost-service.mjs for pricing, by the orchestrator for invocation, and by forge_doctor_quorum for quota preflight (when PFORGE_FOUNDRY_QUOTA_PREFLIGHT=1).

VariableTypeDefaultScopeSet whenSecurity
AZURE_OPENAI_ENDPOINTstring (URL)(none)per-userRouting through Azure OpenAI.Not secret, but reveals tenant name. Example: https://my-resource.openai.azure.com/.
AZURE_OPENAI_API_KEY 🔒string(none)per-userKey-based auth (not Managed Identity).Secret. Prefer AZURE_AUTH_MODE=managed-identity when possible.
AZURE_OPENAI_DEPLOYMENTstring(none)per-userYou need to override the deployment name parsed from the model spec.Not secret.
AZURE_OPENAI_API_VERSIONstring(none)per-userYou need a specific Azure OpenAI API version (e.g. 2024-02-01).Not secret.
AZURE_OPENAI_DEPLOYMENT_TYPEenum"global"per-userPricing is regional or data-zone rather than global. Read by cost-service.mjs.Not secret.
AZURE_OPENAI_ACCOUNT_NAMEstring(none)per-userFoundry quota preflight needs the account name (also accepts AZURE_OPENAI_RESOURCE_NAME as an alias).Not secret.
AZURE_SUBSCRIPTION_IDstring (GUID)(none)per-userFoundry quota preflight or any Azure-RM call.Not secret.
AZURE_RESOURCE_GROUPstring(none)per-userFoundry quota preflight needs the resource group.Not secret.
AZURE_AUTH_MODEstring(unset)per-userSwitching between key-based and identity-based auth. Common values: managed-identity, service-principal, cli.Not secret.

Server ports and network

Set these only if the defaults collide with something else on your machine, or when you need to harden the dashboard bridge with an auth token.

VariableTypeDefaultScopeSet whenSecurity
PLAN_FORGE_HTTP_PORTnumber3100per-machinePort 3100 is taken by another service.Not secret.
PLAN_FORGE_WS_PORTnumber3101per-machinePort 3101 is taken (the WebSocket hub).Not secret.
PFORGE_DASHBOARD_PORTnumber3100per-machineThe CLI needs to open the dashboard on a non-default port (read by pforge open-dashboard).Not secret.
PFORGE_DASHBOARD_URLstring (URL)http://127.0.0.1:3100/dashboardper-machineThe screenshot capture script needs to point at a remote dashboard.Not secret.
PFORGE_BRIDGE_SECRET 🔒string(none)per-machineYou want to require authentication on the MCP bridge endpoints (recommended on multi-user hosts).Secret. Use 32+ random bytes.
PFORGE_AUTH_TOKEN 🔒string(none)per-machineYou want to require a bearer token on the REST API (see MCP Server Reference — REST API).Secret. Use 32+ random bytes.

Project and runtime

Mostly read internally. PLAN_FORGE_PROJECT is the only one you might set yourself, and almost always only in tests.

VariableTypeDefaultScopeSet whenSecurity
PLAN_FORGE_PROJECTstring (path)process.cwd()per-sessionPointing the orchestrator at a project directory other than the working directory (mostly tests).Not secret.
PFORGE_ENVstring"dev"per-machineYou want LiveGuard and the run journal to tag runs with an env label other than dev.Not secret.
PFORGE_LOG_LEVELenum(unset = info)per-sessionDebugging, set to debug to surface cost-service tracing and other diagnostic logs.Not secret.
PFORGE_NO_UPDATE_CHECKboolean (1/0)(unset)per-machineCI environment where reaching out to GitHub for an update check is unwanted.Not secret.

Orchestrator timing

Tune these only when defaults are biting, usually on slow CI runners or when a long-running gate (large vitest suite, integration test, browser test) hits the wall.

VariableTypeDefaultScopeSet whenSecurity
PFORGE_GATE_TIMEOUT_MSnumber (ms)(see orchestrator.mjs)per-sessionA gate's test suite takes longer than the default to run.Not secret.
PFORGE_WORKER_TIMEOUT_MSnumber (ms)(see orchestrator.mjs)per-sessionA worker (slice executor) needs more wall-clock than the default.Not secret.
PFORGE_WORKER_OUTPUT_IDLE_MSnumber (ms)(see orchestrator.mjs)per-sessionA worker is legitimately silent for long stretches (large builds) but should not be killed.Not secret.
PFORGE_BASH_PATHstring (path)(auto-detected)per-machineWindows host with bash in a non-standard location. Plan Forge cannot find bash.exe on PATH and the auto-detection fails.Not secret.

Feature toggles

Opt-in switches for experimental subsystems and bypasses for hardening rails. Most users never touch these.

VariableTypeDefaultScopeSet whenSecurity
PFORGE_DISABLE_TEMPERINGboolean (1/0)(unset)per-sessionYou need to bypass Tempering scans for one run (e.g. running an audit-loop slice that would scan its own scaffolding).Not secret. Use sparingly, this disables quality scans.
PFORGE_FOUNDRY_QUOTA_PREFLIGHTboolean (1/0)(unset)per-machineYou want the orchestrator to check Foundry quota before dispatching slices to an Azure OpenAI deployment.Not secret.
PFORGE_GATE_LINT_STRICTboolean (1/0)0per-sessionYou want gate-lint findings to be hard failures rather than warnings.Not secret.
PFORGE_DRAIN_ON_INITboolean (true/false)trueper-machineYou do not want the MCP server to drain the Tempering queue on startup (CI runners that start and stop the server many times).Not secret.
PFORGE_ALLOW_MASTER_COMMITboolean (1/0)(unset)per-sessionYou explicitly want to allow a commit on master while a run-plan is active (PreCommit hook normally blocks this).Not secret. Discouraged, the guard exists for a reason.
PFORGE_NETWORK_LOG_ONLYboolean (1/0)1per-sessionnetwork.allowed is set and you want the in-process proxy to stay in log-only mode. When 1, the proxy records contacted hostnames but does not block connections.Not secret. Default-on while allowlist enforcement remains advisory.
PFORGE_COST_MODELstring(auto-detected)per-sessionYou want to pin slice pricing to a specific model (subscription mode, e.g. flat Copilot pricing, or a non-default vendor).Not secret.

Telemetry (OpenTelemetry)

Standard OTel variables. When OTEL_EXPORTER_OTLP_ENDPOINT is set, the MCP server auto-enables tracing and ships spans to the configured collector. See Compliance & Data Residency — Observability Export for the full collector setup.

VariableTypeDefaultScopeSet whenSecurity
OTEL_ENABLEDboolean (true/1)(unset)per-machineYou want to force-enable OTel even without an endpoint (useful for local console exporter).Not secret.
OTEL_EXPORTER_OTLP_ENDPOINTstring (URL)(unset)per-machineYou want spans shipped to an OTLP collector. Setting this implicitly turns OTel on.Not secret if the collector is internal; treat as secret if the URL embeds a token.
OTEL_SERVICE_NAMEstring"plan-forge-mcp"per-machineYou run multiple Plan Forge instances and need distinct service names in your APM.Not secret.

Host detection (read-only)

Plan Forge reads these to figure out which IDE or agent CLI is hosting it, so the orchestrator can pick the right routing default and the right model surface. You should never set these yourself, they are populated automatically by the host. Listed here for transparency only.

VariableTypeDefaultSourceWhat it tells Plan ForgeSecurity
NODE_ENVenum(unset)Node.js conventiontest short-circuits hub init and notifications side-effects; production tightens logging.Not secret.
VSCODE_PIDnumber (PID)(set by VS Code)VS CodePlan Forge is running inside VS Code.Not secret.
VSCODE_AGENT_MODEstring(set by VS Code)VS Code Agent Modeenterprise means VS Code Agents Enterprise, Plan Forge picks a different default model route.Not secret.
TERM_PROGRAMstring(set by terminal)Terminalvscode or cursor trigger host-specific routing.Not secret.
CLAUDECODEstring(set by Claude Code)Claude Code CLI1 means Plan Forge is running under Claude Code.Not secret.
CLAUDE_CODE_ENTRYPOINTstring(set by Claude Code)Claude Code CLIAlternate signal for Claude Code detection.Not secret.
CURSOR_TRACE_IDstring(set by Cursor)CursorPlan Forge is running under Cursor; cross-checked with TERM_PROGRAM=cursor.Not secret.
ZED_TERMstring(set by Zed)Zed editorPlan Forge is running under Zed.Not secret.

CLI internal (set transiently by pforge)

These variables are set by the CLI or the orchestrator for the duration of one invocation and then unset. Do not set them in your shell profile, they are documented for transparency and for users writing extensions.

VariableTypeSet byRead byPurposeSecurity
PFORGE_CHILD_MODEboolean (1/0)MCP server when it spawns a child Node processserver.mjsSuppresses double-binding of HTTP/WS ports in the child.Not secret.
PFORGE_RUN_PLAN_ACTIVEboolean (1/0)pforge run-planPreCommit hookTells the master-branch commit guard that the commit is part of an authorised run.Not secret.
PFORGE_QUORUM_TURNboolean (1/0)Orchestrator during quorum dispatchPreAgentHandoff hookSkips LiveGuard context injection during quorum fan-out (one of the documented v3.5+ PreAgentHandoff bypasses).Not secret.
ORG_RULES_FORMAT · ORG_RULES_OUTPUTstringpforge org-rulesorchestratorTells forge_org_rules what format and output path to use.Not secret.
FORGE_SMOKEboolean (1/0)CI smoke-test jobvitest skipIf gateEnables long-running smoke tests (default off in CI).Not secret.

Resolution precedence

When a setting has multiple sources, the orchestrator resolves in this order, first one that yields a non-empty value wins:

  1. CLI flag on the running command (e.g. --quorum=power).
  2. Plan front-matter directive in the plan being executed (e.g. Model: claude-opus-4.7).
  3. Environment variable set in the shell or by the host (most of this appendix).
  4. .forge/secrets.json for the keys listed in the Provider API Keys table.
  5. .forge.json for the keys listed in Appendix T.
  6. Built-in default baked into orchestrator.mjs or capabilities.mjs.

One concrete example. OPENAI_API_KEY is resolved by checking process.env first, then falling back to .forge/secrets.json#OPENAI_API_KEY. The dashboard's Config → Secrets tab writes the latter; CI runners typically rely on the former. Both work; the env-var wins when both are set.

Worked example

A representative shell setup for a developer on Windows running a hybrid Azure-OpenAI / Anthropic stack with the dashboard on a non-default port:

PowerShell profile snippet, representative
# Provider keys (better: store in .forge/secrets.json)
$env:ANTHROPIC_API_KEY = "sk-ant-..."
$env:OPENAI_API_KEY    = "sk-..."

# Azure OpenAI alternative routing
$env:AZURE_OPENAI_ENDPOINT      = "https://contoso-aoai.openai.azure.com/"
$env:AZURE_OPENAI_API_VERSION   = "2024-02-01"
$env:AZURE_OPENAI_DEPLOYMENT_TYPE = "global"
$env:AZURE_AUTH_MODE            = "managed-identity"

# Non-default ports (3100/3101 conflicted with another local service)
$env:PLAN_FORGE_HTTP_PORT = "3110"
$env:PLAN_FORGE_WS_PORT   = "3111"
$env:PFORGE_DASHBOARD_PORT = "3110"

# Tracing to a local OTel collector
$env:OTEL_EXPORTER_OTLP_ENDPOINT = "http://127.0.0.1:4318"
$env:OTEL_SERVICE_NAME           = "plan-forge-mcp-dev"

# Lift gate timeout for a long-running integration suite
$env:PFORGE_GATE_TIMEOUT_MS = "600000"   # 10 minutes

See also