code-forge is configured through environment variables and an optional
gate.yaml file. Most users need only environment variables.
Selects a named backend defined in the backends: key of .code-forge/gate.yaml.
- Default:
session-default(uses theclaudeCLI with the active session model -- no model pin) - Precedence:
--backendCLI flag >FORGE_BACKENDenv >default: trueentry ingate.yaml> session-default
export FORGE_BACKEND=claude-api # use a named API backend
export FORGE_BACKEND=local-claude # use a named CLI backendIf the named backend does not exist in gate.yaml, code-forge exits
with an error listing the configured backend names.
Setting FORGE_BACKEND to an empty string ("") falls through to the
config-file default or session default -- it does not cause an error.
Forces the review outlet. Three outlets are available:
-
subprocess-- spawns a freshclaudesubprocess for each review pass. Requires theclaudebinary in PATH and an authenticated session (or acommandoverride in gate.yaml -- see "Third-Party API Proxies" below). -
inline-- Outlet B: runs the merged review skill inside the current AI session. No subprocess, no reachability probe. -
subagent-- spawns a fresh Agent per pass; works inside the current session without a subprocess. -
Default: auto-detected based on backend reachability probe. Reachable ->
subprocess. Unreachable -> error (FAIL CLOSED, no silent fallback to inline). -
Precedence:
--outletCLI flag >FORGE_OUTLETenv >outletfield ingate.yaml> reachability probe.
export FORGE_OUTLET=subprocess # always use CLI subprocess
export FORGE_OUTLET=inline # always use inline (no subprocess)
export FORGE_OUTLET=subagent # always use subagentWhen FORGE_OUTLET=inline, code-forge never runs the reachability probe.
This is useful when you know the backend is available and want to skip
the probe latency.
"Implicit claude -p is disabled" warning: This warning fires only when no backend is explicitly configured and code-forge would fall back to the implicit
claudeCLI default. The implicit fallback is disabled because it nests a subprocess and bills the main Anthropic account. Settingcommand: claude(or a proxy binary path) in a gate.yamlbackendsentry is an explicit configuration and does NOT trigger the warning. If you see this warning, either add abackendssection to gate.yaml, or setFORGE_OUTLET=inlineto run review inside the current session.
Overrides the model used by CLI backends. Has no effect on API backends
(which use the model configured in gate.yaml or the API default).
- Default:
claude-sonnet-4-6 - Applies to:
type: clibackends only
export FORGE_LLM_MODEL=claude-opus-4-5 # use Opus for reviews
export FORGE_LLM_MODEL=claude-sonnet-4-6 # back to defaultUseful when you want to run reviews with a more capable model without
creating a full gate.yaml backends entry.
Sets the timeout (in seconds) for the backend reachability probe.
- Default:
20seconds - Maximum:
120seconds - Precedence:
FORGE_AUTH_TIMEOUTenv > default (20s)
export FORGE_AUTH_TIMEOUT=45 # increase for slow networks
export FORGE_AUTH_TIMEOUT=5 # decrease for fast local setupsThe probe runs claude auth status --json (not an inference call -- zero
token cost). Successful results are cached for 5 minutes, so the probe
overhead is incurred at most once per 5-minute window.
Values less than 1 or greater than 120 are rejected with a clear error.
Sets the timeout (in seconds) for each LLM invocation during review.
- Default:
120seconds - Precedence: explicit
timeout_sargument >FORGE_LLM_TIMEOUT_Senv >120 - Resolved per call (not frozen at import), so the override takes effect even when the env var is set after the process starts.
export FORGE_LLM_TIMEOUT_S=300 # cross-region or reasoning backendsAn unset, malformed, or non-positive value falls back to 120. Raise this when
a healthy backend call is aborted mid-flight by the default 120s ceiling (slow
cross-region APIs, reasoning models). Distinct from FORGE_AUTH_TIMEOUT, which
bounds the zero-cost reachability probe, not the review inference call.
Update note:
FORGE_LLM_TIMEOUT_Srequires forge v2.4 or later. If you are running an older installation, update viapip install --upgrade code-forgeor reinstall from source. On older builds, the per-call timeout is hardcoded to 120 seconds and cannot be overridden.
The backends: key in .code-forge/gate.yaml defines named backends.
The file is created by code-forge init in the project root under
.code-forge/gate.yaml. If the file does not exist, code-forge uses the
session-default backend (the claude CLI with the active session model).
Backends are a dict keyed by name -- not a list. The key is the backend
name used with FORGE_BACKEND.
backends:
<backend-name>:
type: api | cli
# ... type-specific fieldsThe first entry with default: true is used when FORGE_BACKEND is not set
and no other override applies. If no entry has default: true, the
session-default backend is used.
| Field | Required | Description |
|---|---|---|
type |
yes | Must be "api" |
format |
yes | API format: "anthropic", "openai", or "vertex" |
base_url |
yes (anthropic/openai) | API base URL (see format-specific notes below) |
api_key_env |
yes (anthropic/openai) | Name of the env var that holds the API key |
project_id |
yes (vertex) | GCP project ID |
region |
no (vertex) | GCP region (default: global) |
credentials_path |
no (vertex) | Path to service account JSON key file |
model |
no | Model ID (leave empty to use API default) |
max_tokens |
no | Output token cap (default: 16384) |
default |
no | If true, use this backend when no override is set |
Security note: Never put an API key directly in gate.yaml. Use
api_key_env to specify the name of an environment variable, and set
the actual key in your shell or secrets manager. code-forge rejects any
backend entry that contains an api_key field.
base_url format differences: The format field determines how
code-forge constructs the request URL from base_url:
format: anthropic-- code-forge appends/v1/messagesto the configuredbase_url. For example, ifbase_urlishttps://proxy.example.com/anthropic, the actual request goes tohttps://proxy.example.com/anthropic/v1/messages. Do NOT include/v1/messagesinbase_urlfor anthropic-format backends.format: openai-- code-forge appends/chat/completionstobase_url. Include the version path but NOT the endpoint (e.g.https://api.openai.com/v1, nothttps://api.openai.com/v1/chat/completions).format: vertex--base_urlis not used; the URL is constructed fromproject_idandregion.
| Field | Required | Description |
|---|---|---|
type |
yes | Must be "cli" |
model |
no | Model to pass to the CLI (empty = session default) |
command |
no | CLI binary name or path (default: "claude") |
max_tokens |
no | Output token cap (default: 16384) |
default |
no | If true, use this backend when no override is set |
code-forge supports third-party LLM providers and proxy services through two patterns:
CLI outlet with a proxy binary (type: cli): Replace the default
claude binary with a proxy that accepts the same stdin/stdout contract.
Set command to the path of your proxy binary:
backends:
my-proxy:
type: cli
command: /usr/local/bin/my-llm-proxyThe proxy binary must accept the same arguments and produce the same
output format as claude -p. No API key is needed in gate.yaml -- the
proxy handles its own authentication.
API outlet with a proxy URL (type: api): Point base_url at a
proxy server that implements the OpenAI or Anthropic API format:
backends:
proxy-api:
type: api
format: openai
base_url: https://my-proxy.example.com/v1
api_key_env: MY_PROXY_API_KEYThe proxy must return responses in the format specified by format.
Authentication is handled via api_key_env -- the env var holds whatever
token or key your proxy expects.
This covers hosted routers such as OpenRouter, Together, or a self-hosted
LiteLLM / one-api gateway: point base_url at the router, set format to
the API it speaks (openai for OpenRouter), and put the router's key in
api_key_env. See the OpenRouter example below.
Note: The "authenticated session" mentioned in the
subprocessoutlet description refers to the defaultclaudeCLI using the user's own Anthropic account. Third-party proxies use their own authentication viaapi_key_env(for API outlets) or internal credentials (for CLI proxy binaries).
backends:
claude-api:
type: api
format: anthropic
base_url: https://api.anthropic.com
api_key_env: ANTHROPIC_API_KEY
model: claude-opus-4-5
default: trueSet the key in your shell before running code-forge:
export ANTHROPIC_API_KEY=sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXXXXXXbackends:
openai-compatible:
type: api
format: openai
base_url: https://api.openai.com/v1
api_key_env: OPENAI_API_KEYexport OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXOpenRouter speaks the OpenAI API, so it is a plain format: openai backend
with the router's URL and a prefixed model id. The same shape works for
Together, DeepInfra, or a self-hosted LiteLLM / one-api gateway -- only the
base_url, model, and api_key_env change.
backends:
openrouter:
type: api
format: openai
base_url: https://openrouter.ai/api/v1
api_key_env: OPENROUTER_API_KEY
model: anthropic/claude-sonnet-4.6
default: trueexport OPENROUTER_API_KEY=YOUR_API_KEY_HEREcode-forge sends only Authorization: Bearer <key> and Content-Type; it
does not send OpenRouter's optional HTTP-Referer / X-Title ranking
headers, which are not required for reviews.
backends:
local-claude:
type: cli
model: claude-opus-4-5
command: claudeNo API key needed -- this uses your existing claude auth session, so it is
also the simplest way to review with an account-authenticated model (a
subscription / Pro login rather than an API key).
If you normally drive claude through a shell wrapper that scrubs the
environment to pin a specific account (for example a function that unsets
ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY / CLAUDE_CODE_USE_VERTEX before
exec), two things matter: point command at the claude binary (a shell
function or alias is not resolvable by which), and start the code-forge / MCP
process with the same scrubbed environment -- the spawned claude inherits
code-forge's environment, so a stray ANTHROPIC_BASE_URL would send it to the
wrong endpoint.
Some assistants log in with OAuth / a subscription session rather than an API
key, and their CLIs do not speak code-forge's CLI contract
(-p <prompt> --output-format json), so they cannot be a type: cli backend
directly. The portable way to review with them is a local bridge: run a small
proxy that holds the tool's authenticated session and exposes an OpenAI- or
Anthropic-compatible endpoint on localhost. code-forge then consumes it as an
ordinary API backend -- the same shape as the OpenRouter example, just pointed
at the bridge:
backends:
via-bridge:
type: api
format: openai # or anthropic -- whichever the bridge speaks
base_url: http://localhost:8080/v1
api_key_env: BRIDGE_KEY # most bridges ignore the key; set any value
model: <the model id the bridge exposes>export BRIDGE_KEY=unusedYou provide and run the bridge process; code-forge does not ship one. From code-forge's side it is a plain HTTP backend, so no special support is needed -- if the bridge speaks the OpenAI or Anthropic API, it works.
backends:
claude-api:
type: api
format: anthropic
base_url: https://api.anthropic.com
api_key_env: ANTHROPIC_API_KEY
default: true
openai-compatible:
type: api
format: openai
base_url: https://api.openai.com/v1
api_key_env: OPENAI_API_KEY
local-claude:
type: cli
model: claude-opus-4-5
command: claudeWith this config:
- Default:
claude-api(API call with Anthropic key) FORGE_BACKEND=openai-compatible: use OpenAI APIFORGE_BACKEND=local-claude: use local claude CLI with Opus
code-forge init writes gate.schema.json alongside gate.yaml in the
.code-forge/ directory. The schema file enables IDE tooling that reads
yaml-language-server directives. VS Code and Cursor honor the $schema
directive automatically; PyCharm ignores it and requires manual schema
registration via Settings -> Languages -> JSON Schema Mappings.
Authentication is handled by the claude CLI. Two options:
Option 1: claude auth login (recommended for interactive use)
claude auth logincode-forge verifies authentication by running claude auth status --json.
This is not an inference call -- it has zero token cost.
Option 2: ANTHROPIC_API_KEY env var
If ANTHROPIC_API_KEY is set, the claude CLI uses it automatically.
No claude auth login needed.
export ANTHROPIC_API_KEY=sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXXXXXXSet the env var named by api_key_env in your shell:
# For a backend with api_key_env: ANTHROPIC_API_KEY
export ANTHROPIC_API_KEY=sk-ant-api03-XXXXXXXXXXXXXXXXXXXXXXXXXXXX
# For a backend with api_key_env: OPENAI_API_KEY
export OPENAI_API_KEY=sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXThe reachability probe for API backends checks only that the env var is set (non-empty). It does not make a network request or validate the key. A set but invalid key will fail at review time, not at probe time.
If the reachability probe times out (especially on slow networks or when
the claude binary is slow to start):
export FORGE_AUTH_TIMEOUT=60 # wait up to 60 secondscode-forge's review pipeline sends multiple LLM prompts per review run. Each of the 3 review passes (qodo, expert, adversarial) sends the full diff plus review context to the backend, and the pipeline runs multiple rounds until convergence. A typical review run produces 9 or more LLM calls, each containing the full diff.
Reasoning models (claude-opus-4-5, claude-sonnet-4-6 with extended thinking, mimo-pro, deepseek-reasoner) add substantial thinking overhead per call. Multiplied across 9+ passes, this overhead dominates total review time and cost.
Recommendation: Use a fast non-reasoning model as the primary review backend. Reserve reasoning models for single-pass deep analysis or targeted falsification of specific findings.
| Category | Models | Multi-pass review | Single-pass analysis |
|---|---|---|---|
| Fast (recommended for review) | haiku, mimo, deepseek-chat, glm-4 | Good | Adequate |
| Balanced | claude-sonnet-4-6, gpt-4o | Acceptable | Good |
| Reasoning (use sparingly) | claude-opus-4-5, mimo-pro, deepseek-reasoner | Slow and expensive | Best depth |
For cross-region backends (e.g. CN-hosted APIs accessed from outside
China), also raise FORGE_LLM_TIMEOUT_S to account for network latency
(300-600 seconds is typical).
Common backend errors and their solutions:
| Symptom | Likely Cause | Fix |
|---|---|---|
| L1 pass always times out | Backend too slow or unreachable | Raise FORGE_LLM_TIMEOUT_S (e.g. 300 for cross-region) or switch to a faster model |
| "unexpected response structure" | Backend returned non-JSON or truncated response | Check max_tokens setting in gate.yaml -- ensure it is at least 16384. Reasoning models need higher limits |
| "Implicit `claude -p` is disabled" | No backend configured; implicit fallback refused | Add a backends section to gate.yaml, or set FORGE_OUTLET=inline |
| "LLM subprocess failed" | CLI binary not found or crashed | Verify the command path exists and is executable (which claude or which <proxy>) |
| "schema validation failed" | Backend returned valid JSON but wrong structure | Verify the backend supports the API format specified by format in gate.yaml |
| "lock busy" | Another code-forge process is running on this project | Wait for it to finish, or check for stuck processes (ps aux | grep code-forge) |
| Exit code 6 (TIMEOUT) | 5 consecutive L1 timeouts tripped the circuit breaker | The backend cannot keep up. Reduce diff size, raise FORGE_LLM_TIMEOUT_S, or switch to a faster (non-reasoning) backend |
Exit code 6 is distinct from exit code 1 (review found unfixed issues). The circuit breaker trips after 5 consecutive L1 timeouts to prevent the review from running indefinitely on an unreachable or overloaded backend. The counter resets on any successful L1 call, so transient single-request timeouts do not trip the breaker.
An opt-in objective laziness check for the inline review outlet. When enabled, forge plants semantic defects into an isolated copy of the diff and gates on how many the reviewer catches. A rubber-stamp reviewer that returns empty findings is detected and flagged UNRELIABLE (exit 7).
Add a canary: block to .code-forge/gate.yaml:
canary:
enabled: true # bool -- required for opt-in
n: 5 # int, 3..5 -- canaries to plant per review
threshold_ratio: 0.6 # float, >0.0..1.0 -- catch ratio to pass| Field | Type | Range | Default | Description |
|---|---|---|---|---|
enabled |
bool | -- | -- | Activates the canary check. Equivalent to passing --canary on the CLI. Required for opt-in. |
n |
int | 3..5 | 5 | Number of canary mutations to plant. |
threshold_ratio |
float | >0.0..1.0 | 0.6 | Minimum fraction of canaries the reviewer must catch. The actual threshold is ceil(threshold_ratio * n) (e.g. 0.6 * 5 = 3). A value of 0.0 is rejected because it would produce a threshold of 0, which is meaningless. |
- With no opt-in (no
--canaryflag, nocanary:block, orcanary.enabled: false), the inline outlet is unchanged -- it returnsDELEGATED(exit 5) as before. - When fewer than 2 verified canaries can be generated, the check is skipped with a notice (graceful degradation, not a hard failure).
- If the canary dispatch fails (LLM timeout, network error), the check
degrades to
DELEGATED-- it never crashes the review. - The canary result never alters outlet or model selection (D-16).
- Planted defects are never written to the working tree or git history. They exist only in the isolated review copy passed to the fresh-context reviewer.
- Currently Python diffs only; non-Python diffs skip the canary with a notice.
The --canary flag on code-forge review achieves the same opt-in with
default n=5 and threshold_ratio=0.6, without requiring a gate.yaml
canary: block.
- VS Code setup -- setting env vars in VS Code terminal
- Cursor setup -- setting env vars in Cursor terminal
- PyCharm setup -- setting env vars in PyCharm
- README Backend configuration -- quick reference