Environment variables
Environment variables are the right lever for two situations a config file
handles badly: one-off debugging (FREECODE_DEBUG=1 freecode run "…") and
CI, where there is no home directory worth persisting to.
Nothing here is required. Every variable has a working default, and the two
things you might genuinely need — an API key and a model — are better kept in
~/.freecode/config.json.
Two things to know before using the tables:
- When a variable is read matters. Most are read per call, so exporting one
in a long-running
freecode servetakes effect on the next turn. A few are captured once at process start and need a restart. The “Read” column says which. 0means different things in different rows. For timeouts it disables the guard; forFREECODE_READ_DEDUPandFREECODE_CACHE_MISS_NOTICESit turns a feature off; forFREECODE_IDLE_NUDGE_TOKENSit disables the nudge. Nothing here treats an unparseable value as0— a typo falls back to the default, because silently disabling a cost guard is the worse failure.
Credentials
| Variable | Used by |
|---|---|
ANTHROPIC_API_KEY | Anthropic provider |
OPENAI_API_KEY | OpenAI provider |
GEMINI_API_KEY | Gemini provider |
MINIMAX_API_KEY | MiniMax (also covers minimax-coding-plan) |
DEEPSEEK_API_KEY | DeepSeek |
ZAI_API_KEY | Z.ai |
BRAVE_API_KEY | websearch uses the Brave API when set; DuckDuckGo’s keyless HTML endpoint otherwise |
config.json wins over the environment. getApiKey() checks
providers.<id>.apiKey first and only then the variable, so if a key is saved in
the app, exporting a different one changes nothing. A provider suffixed with
-coding-plan falls back to its base provider’s key and variable.
| Variable | Values | Effect |
|---|---|---|
FREECODE_ANTHROPIC_AUTH | oauth, api-key | pins how anthropic authenticates, overriding providers.anthropic.authMode |
oauth bills a Claude Pro/Max subscription instead of a key, and setting it is
one of the three ways to opt in. It is not a neutral switch — see
Anthropic subscription login for what
the OAuth path does and the risk it carries.
Provider requests
| Variable | Default | Read | Effect |
|---|---|---|---|
FREECODE_HEADER_TIMEOUT_MS | 300000 | per request | how long a provider may take to return response headers. Cleared the instant they arrive, so it never caps generation time. 0 disables |
FREECODE_SSE_STALL_TIMEOUT_MS | 180000 | per request | how long a live stream may be completely silent. Not a token budget: a slow model that emits keep-alives is healthy. 0 disables |
FREECODE_CACHE_TTL | 5m | per call | 5m or 1h. 1h opts into Anthropic’s extended prompt cache. Any other value warns and falls back to 5m |
FREECODE_CACHE_MISS_NOTICES | on | per call | set 0 to stop surfacing “prompt cache went cold / missed” notices |
FREECODE_DEBUG_CACHE | off | per call | 1 logs a fingerprint of every cacheable prompt segment, for diagnosing a low hit rate |
Both timeouts live in a fetch wrapper below the AI SDK
(providers/fetch-timeout.ts) so every SSE byte counts. Measuring liveness above
the SDK’s normalizer counts only 5 part types and misses tool-input-delta,
which made a large write look like a dead stream.
FREECODE_FIRST_CHUNK_TIMEOUT_MS,FREECODE_STREAM_STALL_TIMEOUT_MS, andFREECODE_REQUEST_TIMEOUT_MSwere the previous generation of this guard and no longer exist. If you have them in a shell profile, delete them — they do nothing.
Context and spend
| Variable | Default | Read | Effect |
|---|---|---|---|
FREECODE_COMPACT_TARGET_TOKENS | 120000 | per call | the cost ceiling compaction aims for. Raise it to keep more history; lower it to compact aggressively on a small quota |
FREECODE_AUTO_COMPACT_TOKENS | unset | per call | force auto-compaction at N tokens instead of just below the model’s window. Mostly a testing lever — the real trigger is unreachable by hand |
FREECODE_MAX_TURN_TOKENS | unset | per call | per-run spend circuit breaker in billed tokens (input + output). Off by default; this is a backstop for runaway oscillation, not an operating limit |
FREECODE_IDLE_NUDGE_TOKENS | 100000 | per call (TUI) | context size below which the “your cache has gone cold” nudge never fires. 0 disables it |
Values must be positive integers; anything else logs a warning and keeps the default, because a typo here looks exactly like compaction being broken.
Tools
| Variable | Default | Read | Effect |
|---|---|---|---|
FREECODE_TOOL_RESULT_BUDGET_CHARS | 200000 | process start | total characters of tool results kept verbatim in context before older ones are replaced with a marker |
FREECODE_OUTPUT_MAX_CHARS | 30000 | process start | chars of a single tool output shown to the model (head + tail) |
FREECODE_OUTPUT_TAIL_CHARS | 6000 | process start | how much of that budget is spent on the tail. Head is the remainder |
FREECODE_OUTPUT_LINES | 200 | process start | default line window per output call |
FREECODE_OUTPUT_STORE_BYTES | 16777216 | process start | per-session output store budget; oldest outputs evict first |
FREECODE_OUTPUT_STORE_SESSIONS | 50 | process start | how many per-session stores stay live |
FREECODE_READ_DEDUP | on | per call | 0 makes read send the full file body every time instead of eliding an unchanged re-read |
FREECODE_READ_LINE_NUMBERS | off | per call | 1 restores the per-line N: prefix on read output; the range footer stays either way. Off by default since the D3 A/Bs measured −10.9%/−6.8% tokens with quality unchanged |
FREECODE_BASH_COMPRESS | off | per call | 1 turns on content-aware compression of large bash output: build-log noise collapses, search matches and diffs never do. Off until its A/B earns the default |
FREECODE_LSP_SERVERS | unset | per call | JSON map of extension → server, e.g. {".rs":{"command":"rust-analyzer","args":[]}}. Extends the built-in registry |
FREECODE_RTK | on | per call | 0 disables the built-in hook that rewrites ls-style bash commands into compact rtk equivalents |
FREECODE_TASK_NOTIFY | on | per spawn | 0 stops finished background sub-agents and shells from notifying the agent, so no turn starts without your input. agent(run_in_background) then runs in the foreground; background shells still run. Beats tasks.notify in settings.json. See long-running commands |
Memory
| Variable | Default | Effect |
|---|---|---|
FREECODE_DISABLE_MEMORY_EXTRACTION | unset | 1, true, or yes turns off automatic extraction. Overrides memory.autoExtract in settings.json |
Loop health
| Variable | Default | Effect |
|---|---|---|
FREECODE_DISABLE_REDIRECT | unset | 1, true, or yes turns off trajectory redirection. Overrides redirect.enabled in settings.json; the feature is off by default anyway, so this is the kill switch for a project that turned it on |
Evals
| Variable | Default | Effect |
|---|---|---|
FREECODE_EVALS_DIR | ./evals | where freecode eval looks for <suite>.jsonl and quarantine.txt. The default is resolved against the current working directory, so set this to run the suite from anywhere but the repo root |
FREECODE_EVAL_TRIAL_TIMEOUT_MS | 300000 | wall-clock cap per trial. The backstop for a trial that blocks on something without a timeout of its own; a trial that trips it is a failed trial, not a crashed suite |
FREECODE_EVAL_VERIFY_TIMEOUT_MS | 60000 | cap on a coding case’s verify command, spawned in the sandbox after the turn |
FREECODE_EVAL_KEEP_SANDBOX | unset | 1 keeps the case’s tmpdir instead of removing it, and prints the path — how you reproduce a red coding case by hand |
FREECODE_EVAL_HOME | ~/.freecode | where eval_report.json (last run) and eval_runs.jsonl (history) are written. Changing it between runs resets the gate’s baseline |
FREECODE_JUDGE_PROVIDER | unset | provider that grades the judged suite. Required for it and for pnpm eval:gate; with none set the cases still run but the gate closes |
FREECODE_JUDGE_MODEL | provider default | judge model. Must not resolve to the model under test — a collision throws before any case runs |
See eval harness.
Diagnostics
| Variable | Effect |
|---|---|
FREECODE_DEBUG | any non-empty value enables logger.debug output. All logs go to stderr — core speaks JSON-RPC on stdout, so logging there would corrupt the protocol |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT | OTLP endpoint used by freecode trace --otlp when no URL is passed. Checked before the next row |
OTEL_EXPORTER_OTLP_ENDPOINT | same, the generic form |
OTEL_EXPORTER_OTLP_HEADERS | comma-separated k=v list, per the OTLP spec — this is how you pass a Langfuse or Phoenix auth header |
Traces are exported from the recorded event log, never from the hot path, so a slow collector cannot slow down a turn.
Paths and packaging
| Variable | Set by | Effect |
|---|---|---|
FREECODE_BUNDLED | the release binary (build:bun) | 1 means “self-contained binary”: core is bundled rather than spawned from disk, the background update check runs, and the native-library re-exec happens |
FREECODE_BUILD_VERSION | the build | the version reported by --version and the TUI. Falls back to package.json, then unknown |
FREECODE_NO_UPDATE | you | 1, true, or yes disables the background update check, so no “update available” line appears. Nothing self-installs either way — a version pins itself. An explicit freecode update still updates |
FREECODE_ROOT | you | monorepo root used by the TUI and the VS Code extension to locate core when it is not bundled. Set it if you run a frontend from outside the repo |
FREECODE_HOME | you | only the updater’s builds/stable/freecode lookup honors this. It does not relocate your data — see known gaps |
CLAUDE_CONFIG_DIR | you | where Claude Code sessions are read from for the import tab. Defaults to ~/.claude; read-only, FreeCode never writes there |
CDP_URL | you | Chrome DevTools endpoint for the legacy browser path. Defaults to http://localhost:9222 |
FREECODE_BUN_TARGET, FREECODE_BUN_OUTFILE | CI | cross-compile target and output path for pnpm build:bun. Contributor-facing only |
__FREECODE_REEXECED is an internal sentinel the binary sets on itself to avoid
a re-exec loop when it re-launches with the native-library path set. Do not set
it.
Not read — exported
Hook commands receive CLAUDE_SESSION_ID, CLAUDE_TOOL_NAME,
CLAUDE_TOOL_INPUT, CLAUDE_CWD, CLAUDE_AGENT_ID, and CLAUDE_AGENT_TYPE in
their environment. FreeCode sets those for your hook; setting them yourself does
nothing. See hook events.
Known gaps
Found while writing this page; each is also tracked in TODO.md.
FREECODE_HOMElooks like a data-root override and is not one. It is read in exactly one place — the updater’sstablesymlink path (apps/tui/src/entry.ts:101). Every other path (config.json,sessions/,projects/,rollout/,history.jsonl) is built fromos.homedir()directly, so setting it produces a half-relocated install. Either honor it everywhere through onefreecodeHome()helper, or rename it to what it actually controls.- Six variables need a restart, and nothing says so. The
tools/output-storeconstants andTOOL_RESULT_BUDGET_CHARSare module-loadconsts, while the compaction and cache variables are read per call — a deliberate choice there (“the long-lived daemon and the tests can both change it without a module reload”) that was simply not applied to the tool budgets. FREECODE_TOOL_RESULT_BUDGET_CHARS=""silently means zero.Number("")is0, which is finite, so an empty export sets the budget to 0 instead of falling back to the default the way every other numeric variable does.- No
--env-style listing. There is no command that prints the effective values, so diagnosing “why is it compacting so early” means reading source. Afreecode config envthat dumps name / default / effective / source would pay for itself.