Docs Configuration

Configuration reference

Every config.toml key and environment variable, their defaults, and which layer wins.

Every key in config.toml and every environment variable coding.sh reads.

Where settings come from

Later layers win:

  1. Built-in defaults.
  2. ~/.coding.sh/config.toml (or $CODING_SH_HOME/config.toml), your user config.
  3. <repo>/.coding.sh/config.toml, the nearest one above the working directory.
  4. Environment variables (CODING_SH_STORE_URL, CODING_SH_BLOB_URL, and the server variables).
  5. Command-line flags (--store, --blobs, and each command's own options).

A repo file arrives with git clone, so it may only make capture more private:

  • It can set capture and snapshot keys. Redaction lists are merged, not replaced.
  • capture.mode can only become metadata, and capture.maxInlineBytes can only go down.
  • It can add refine.protect patterns but not turn refine.autoProtect off.
  • It can never set sinks or stores.
shell
coding.sh config path                          # the file in use
coding.sh config get                           # the effective config, all layers applied
coding.sh config get capture.mode
coding.sh config set capture.mode metadata --dry-run
coding.sh config set capture.redact '["secrets","env"]'

config set accepts the keys marked settable below, parses numbers, true/false and JSON arrays, and keeps the rest of the file as it is. Edit the file by hand for sinks, prices, gateway and serve settings.

String values in store.*, sinks.*.endpoint, sinks.*.headers and gateway.apiKey may use ${NAME}; it is replaced with that environment variable, and an unset variable is an error. Use it to keep tokens out of the file.

[store]

KeyDefaultSettableWhat
store.indexsqlite://~/.coding.sh/index.dbyesIndex store URL. sqlite:// and memory:// are built in; postgres:// needs the coding.sh-store-postgres package. Another scheme loads coding.sh-store-<scheme>.
store.blobsfile://~/.coding.sh/blobsyesBlob store URL for large payloads. s3:// needs coding.sh-store-s3. With a postgres:// index and no blob URL, blobs go to the same database.

[capture]

KeyDefaultSettableWhat
capture.mode"full"yesfull keeps prompts, replies and tool input and output. metadata keeps ids, times, kinds, durations, token counts and short labels only.
capture.redact["secrets"]yesBuilt-in redactors: secrets (API keys, tokens, private keys), env (values of environment variables), email (email addresses), pii (all of email, phone, card, iban and national-id), phone (phone numbers with a + country code, and North American numbers), card (card numbers with a known issuer prefix that pass the Luhn check), iban (IBANs with the right length for their country and a valid check), national-id (US SSNs, UK National Insurance numbers, Canadian SINs that pass the Luhn check), none (clears the list in your user config).
capture.redactPatterns[]yesExtra regular expressions to redact. Matches become [redacted:<kind>].
capture.exclude[]yesPath globs (*, **, ?, ~). Sessions whose working directory, or a parent of it, matches are dropped at ingest.
capture.maxInlineBytes4096yesPayloads larger than this go to the blob store. Minimum 256.

Redaction runs at ingest, before anything is written to the index, blobs or sinks.

[snapshot]

Used by coding.sh run --, which snapshots the repo before and after the command.

KeyDefaultSettableWhat
snapshot.enabledtrueyesTake repo snapshots.
snapshot.maxFiles20000yesSkip the snapshot for repos with more files than this.
snapshot.timeoutMs2000yesGive up on a snapshot after this many milliseconds.

[retention]

Applied by coding.sh gc.

KeyDefaultSettableWhat
retention.keepSpoolDays7yesDays to keep spool files after they were ingested and sent to every sink. The current UTC day's file is kept even at 0: hooks are still writing to it.
retention.toolOutputDays90yesDays to keep blobs referenced only by tool output and file reads. Diffs and eval tasks are kept.

[update]

KeyDefaultSettableWhat
update.checktrueyesCheck for a new release in the background, at most once a day. coding.sh serve never checks.

[[sinks]]

A sink copies events to another system as they are ingested. Add one table per sink. Sinks run for local ingest, the local OTLP receiver (coding.sh collector) and coding.sh serve, which mirrors each committed batch after it answers the request. The collector and serve send an event once: a request that is posted again, such as a client retry, adds nothing to a sink.

Local ingest sends what it stores from the hook spool and what it imports from agent transcripts (coding.sh ingest --backfill, coding.sh init, and the import a read runs for agents without hooks).

  • A sink that fails does not stop ingest. It is tried again after a backoff, then catches up from where it stopped in the spool.
  • For transcripts it starts again at the top of each file it missed part of, so it can get a transcript event twice. A sink added later gets the transcripts still on disk the same way.
  • With sinks set, one transcript import runs at a time (~/.coding.sh/sinks.lock).
config.toml
[[sinks]]
kind = "otlp"                          # or "http"
name = "team"
endpoint = "https://obs.example.com"   # otlp posts to <endpoint>/v1/traces
mode = "metadata"
headers = { Authorization = "Bearer ${CODING_SH_INGEST_KEY}" }
KeyDefaultWhat
kindrequiredotlp (OTLP/HTTP JSON traces) or http (coding.sh/trace JSON). Another kind loads coding.sh-sink-<kind>.
endpointrequiredWhere to send.
mode"metadata"metadata sends no prompts, code or tool output. full sends everything that was captured.
namethe kindUnique name: letters, digits, ., _, -. Needed when two sinks share a kind.
batchSizeEvents per request.
headersTable of HTTP headers.

The otlp sink sends a session as one invoke_agent span when the session ends. From coding.sh collector and coding.sh serve it also sends that span while a session is open, so a session that never ends has one (Forward and sinks). Local ingest sends it again when a transcript import adds events to a session without ending it, so the span has the session's totals.

[prices]

One [prices."<model>"] table per model, with prices in USD per million tokens. Ingest and coding.sh serve use them to estimate cost for model calls that report tokens but no cost. They replace the built-in row for that model, add models it lacks, and also price newer versions in the same family ([prices."house-coder-2"] prices house-coder-2.1 too). A cost the agent or gateway reported is never replaced.

config.toml
[prices."my-finetune"]
input = 3.0
output = 15.0
cache_read = 0.3
cache_write = 3.75

input and output are required. cache_read and cache_write are optional and default to the input price. [pricing."<model>"], the older spelling, still works; [prices] wins a model set in both. Stored estimates are recomputed the next time the index opens after a change.

Long-context and service-tier rates are optional sub-tables:

config.toml
[prices."my-finetune".long_context]   # prompts over `above` tokens (input + cache)
above = 200000
input = 6.0
output = 22.5

[prices."my-finetune".batch]          # also flex, priority, fast, ultrafast
input = 1.5
output = 7.5

[prices."my-finetune".batch.long_context]   # `above` defaults to the model's threshold
input = 3.0
output = 11.25

Each sub-table takes the same input, output, cache_read and cache_write keys, plus cache_write_1h for Anthropic 1-hour cache writes (it defaults to cache_write). A geo table multiplies every rate for calls that ran in that inference geography:

config.toml
[prices."my-finetune".geo]   # payload.inferenceGeo, from Anthropic's usage.inference_geo
us = 1.1

A call goes to the tier it recorded (batch, flex, priority), or fast for Claude fast mode (fast falls back to priority rates when it has no table). A tier with no table is priced at the standard rates.

An override replaces the whole built-in row, tiers included, so add the tier tables you want to keep. See Where cost comes from.

[gateway]

For LLM gateways (coding.sh gateway, compare --gateway, and gateway judges). The top-level keys are for Vercel AI Gateway. Other gateways get their own table, [gateway.<name>], with the same keys. Names: openrouter, litellm, cloudflare, portkey, helicone, requesty, ramp, truefoundry, kong, bifrost, martian.

KeyDefaultWhat
apiKeyEnvthe gateway's standard variable, e.g. "AI_GATEWAY_API_KEY", "OPENROUTER_API_KEY"Environment variable that holds the key.
apiKeyThe key itself, usually "${SOME_VAR}". Used only when the variable above is empty.
baseUrlthe gateway's API, e.g. https://ai-gateway.vercel.sh/v1API base. For Vercel, CODING_SH_GATEWAY_URL overrides it. Required for self-hosted LiteLLM unless LITELLM_PROXY_API_BASE is set.
defaultTop level only: the gateway a bare compare --gateway uses.
accountId, gatewayId[gateway.cloudflare] only: where the logs live. CLOUDFLARE_ACCOUNT_ID also works.

Keys are never printed or stored. See LLM gateways.

[refine]

Which harness lines an edit may not delete, reword or relax (coding.sh refine check, refine test, refine run and exp ab), and which refine propose marks READ-ONLY. See Protected safety lines. Also how far a listed cost may rise for a pass-rate gain in refine test|run --suite; see Climbing on an eval suite.

KeyDefaultWhat
protect[]Regexes. An AGENTS.md or other instruction-file, rule or skill line matching one is protected; the line is tested without its list marker or bold, so "^Use pnpm" matches - Use pnpm. A repo config's patterns are added to yours.
autoProtecttrueProtect gate-word + safety-topic lines ("Never run git push --force") automatically. Honoured only in your user config.
maxCostRise"50%"The default of --max-cost-rise: how far a listed cost, tokens, time, turns or size objective may rise for a significant pass-rate gain. "100%", "none", or a number: below 1 a fraction (0.5 is +50%), from 1 up percent (150 is +150%). A repo config can lower it but not raise it.
canary_modelnoneThe model refine test --suite and refine run --suite re-test kept candidates under when --canary-model is not given, and the default for refine recheck --model. See Canary re-test. Honoured only in your user config.
config.toml
[refine]
protect = ["^Use pnpm", "staging"]

[proxy]

Read by coding.sh proxy and coding.sh init --proxy. Flags on coding.sh proxy win over these, and these win over the upstream init --proxy chained through (the base URL an agent used before, kept in ~/.coding.sh/proxy-installs.json). See Capture with the proxy.

KeyDefaultWhat
port4320Port the proxy listens on, and the one init --proxy points agents at.
host127.0.0.1Interface to bind.
anthropic_upstreamhttps://api.anthropic.comWhere Anthropic API calls go, e.g. a gateway.
openai_upstreamhttps://api.openai.comWhere OpenAI API calls go.
chatgpt_upstreamhttps://chatgpt.com/backend-api/codexWhere Codex calls signed in with ChatGPT go.

[serve]

Read only by coding.sh serve. Every key has an environment variable, which wins over the file. PRODUCTION.md §1.1 has the full rules.

KeyEnvDefaultWhat
serve.hostCODING_SH_HOST127.0.0.1Interface to bind.
serve.portCODING_SH_PORT, then PORT4318Port.
serve.retention_daysCODING_SH_RETENTION_DAYS0 (keep)Delete sessions older than this.
serve.logCODING_SH_LOGjson when stderr is not a terminaljson or text.
serve.queue_max_eventsCODING_SH_QUEUE_MAX_EVENTS50000Ingest queue limit; over it, requests get 429.
serve.queue_max_bytesCODING_SH_QUEUE_MAX_BYTES134217728Same, in bytes.
serve.insecureCODING_SH_INSECURE=1offAllow a non-loopback bind without keys.
serve.trust_proxyCODING_SH_TRUST_PROXY=1offHonor X-Forwarded-Proto and X-Forwarded-For.
serve.cors_origins[]Browser origins allowed to post to the ingest routes (/v1/ingest, OTLP). The viewer and /api/* stay same-origin.
[[serve.projects]] id, ingest_keys, retention_daysCODING_SH_INGEST_KEYSProjects and their ingest keys (sha256:<hex> in the file).
[serve.viewer] read_keysCODING_SH_READ_KEYSRead keys: [{ project = "*", key = "sha256:<hex>", name = "alice" }]. name (optional) is recorded as the author of that key's labels.
[serve.viewer] basic_user, basic_password_sha256CODING_SH_BASIC_AUTH (user:password)Basic auth for the viewer.
capture.modeCODING_SH_CAPTURE_MODEfullmetadata drops content on ingest.
[serve.forward] endpointCODING_SH_FORWARD_URLunset (no forward)OTLP/HTTP collector that serve sends every committed trace to, as JSON. /v1/traces is added unless the URL already ends in it. ${ENV} works in the file. PRODUCTION.md §1.9
[serve.forward] headersCODING_SH_FORWARD_HEADERS (k=v,k2=v2, URL-encoded values)Headers for the collector, such as an API key. The variable's entries are added to the table's.
[serve.forward] modeCODING_SH_FORWARD_MODEcapture.modemetadata sends ids, names, models, tokens, timings and status only. full sends attributes after redaction.
[serve.forward] max_buffer_spansCODING_SH_FORWARD_MAX_BUFFER_SPANS20000Spans kept in memory while the collector is down. Past it the oldest are dropped and counted.

Other files

FileUsed byReference
coding.sh.toml at the repo rootcoding.sh toolevals (targets, drivers, variants, [toolevals] confirmAboveUsd, default 5)
bench/<name>.toml or ~/.coding.sh/benchmarks/<name>.tomlcoding.sh benchcoding.sh bench new --help
An experiment spec (JSON)coding.sh exp run <spec>
A harness TOMLcoding.sh harness snapshot --filecoding.sh harness snapshot --help

Environment variables

Paths and stores

VariableWhat
CODING_SH_HOMEData directory. Default ~/.coding.sh. Use a temp dir in tests and CI to keep runs out of your history.
CODING_SH_STORE_URLIndex store for every command; same as --store. Skips the automatic ingest of the local spool.
CODING_SH_BLOB_URLBlob store; same as --blobs.
CODING_SH_DEV_REAL_STORE=1Lets coding.sh run from a source checkout (src/*.ts, or a dist/ built inside it) open the default home. Without it, or CODING_SH_HOME or a store URL, such a run stops before it opens anything. Migrations still wait for the install that owns the store (Troubleshooting).
CODING_SH_SCAN_CAPHow many sessions usage and cache doctor read for a window's totals before they stop and say so (Stopped after N sessions, truncated in --json). Default 100000. Tests set a small one.
CODING_SH_PURGE_WAIT_MSHow long purge waits for another process's read of the index or the blob pack before it reports that the old bytes stay. Default 10000 for the index checkpoint before VACUUM and 5000 after it. Tests set a short wait.
DATABASE_URLcoding.sh serve only: the store when it is a postgres:// URL and CODING_SH_STORE_URL is unset.

Capture

VariableWhat
CODING_SH_DISABLE=1The hook reads its input and writes nothing. Set it in an agent's environment to pause capture.
CODING_SH_NO_AUTO_INGEST=1The hook does not start a background ingest when a session ends, and init does not finish its import of past sessions in the background.
CODING_SH_NO_PROXY_AUTOSTART=1The hook does not restart a stopped coding.sh proxy when a session starts (it does only after init --proxy).
CODING_SH_ECHO_CONTINUE=1The hook prints {"continue":true}, for agents that need a JSON reply. init sets this up where needed.
CODING_SH_HOOK_SOURCEPath of the hook.mjs that init copies into ~/.coding.sh/bin.
CODING_SH_INIT_IMPORT_BUDGET_MSHow long init imports past sessions before it hands the rest to a background import. Default 8000.
CODING_SH_CAPTURE_MODEcoding.sh serve and the SDKs: full or metadata.

Set by coding.sh for the processes it starts

Read these in your agent or script to tie its output to a run.

VariableSet byWhat
CODING_SH_SESSION_IDrun --The session id of the wrapped command.
CODING_SH_RUN_IDexp run, compare, bench, workflowsRun id. Hook events carrying it are tagged with the run, and the hook skips its own background ingest. You can set it yourself to tag a run.
CODING_SH_AGENT, CODING_SH_AGENT_OUTPUT, CODING_SH_AGENT_STDOUT, CODING_SH_AGENT_DIFFcompare, exp run eval tasks (verifier only)The agent adapter id, a file with the agent's final answer, a file with its whole stdout (for Claude Code, stream-json with every tool call), and a file with its work-tree diff (taken before hidden files land; judge grade reads it for agent-slop). The files sit outside the work tree. CODING_SH_SESSION_ID is set too when the run produced a session.
CODING_SH_GRADER_USAGEcompare, exp run eval tasks (verifier only)A JSONL file. judge grade appends one row per model call (model, tokens, cost) so the run's judge spend counts in compare's spend lines and --max-cost.
CODING_SH_CASE_ID, CODING_SH_TRIALexp run command tasksThe case id and trial number.
CODING_SH_ATTEMPT, CODING_SH_SEQ_Kexp run --seq K command tasksThis attempt (from 1) and K, the most attempts a case gets. With envelope = true stdin carries them too, with the earlier attempts' feedback in history. See Sequential attempts.
CODING_SH_WORKFLOW_RUN_IDworkflows run, workflows replayWorkflow run id.
CODING_SH_BENCH, CODING_SH_BENCH_SEED, CODING_SH_BENCH_DATE, CODING_SH_BENCH_RUN_ID, CODING_SH_BENCH_CASESbench run command sourcesBenchmark name, seed, date, run id and number of cases to print.
CODING_SH_BENCH_NOWyou, in testsMake bench run act as if it started at this time (ISO time or epoch ms), to simulate daily runs.
CODING_SH_BENCH_SEED_KEYyou, or a CI secretKey for seed = "secret" benchmarks: each date's salt is an HMAC of the date under it. Rename it per benchmark with seedKeyEnv.

Server

CODING_SH_HOST, CODING_SH_PORT, PORT, CODING_SH_INGEST_KEYS, CODING_SH_READ_KEYS, CODING_SH_BASIC_AUTH, CODING_SH_INSECURE, CODING_SH_TRUST_PROXY, CODING_SH_LOG, CODING_SH_RETENTION_DAYS, CODING_SH_QUEUE_MAX_EVENTS, CODING_SH_QUEUE_MAX_BYTES: see [serve].

Evals and integrations

VariableWhat
ANTHROPIC_API_KEYLLM judges with "provider":"anthropic" (the default). A grader's apiKeyEnv names another variable.
OPENAI_API_KEYLLM judges with "provider":"openai".
CLAUDE_CODE_OAUTH_TOKENLLM judges with "provider":"claude-code", and exp ab and refine with --provider claude-code, which run claude -p on a Claude subscription (the token comes from claude setup-token). Without it the CLI uses ANTHROPIC_API_KEY or its own login. With no API key set, exp ab and refine use it on their own.
CODEX_HOMEWhere "provider":"codex" and --provider codex find Codex's auth.json (default ~/.codex); each call gets a throwaway copy.
AI_GATEWAY_API_KEYVercel AI Gateway: gateway enrich, gateway status --credits, compare --gateway, gateway judges.
CODING_SH_GATEWAY_URLVercel AI Gateway API base.
OPENROUTER_API_KEY, LITELLM_PROXY_API_KEY, CLOUDFLARE_API_TOKEN, PORTKEY_API_KEY, HELICONE_API_KEY, RAMP_ROUTER_API_KEYOther gateways: gateway enrich lookups and compare --gateway <name>.
CODING_SH_EXP_SESSIONExperiment session id for exp run and exp session, in place of the nearest .coding-sh/session.json.
CODING_SH_RETRY_BASE_MSFirst backoff (ms) before a trial is retried after a rate limit, 5xx or network error; it doubles per retry, with jitter. Default 2000. Tests set 0.
CODING_SH_CONFIRM_ABOVE_USDworkflows run and replay ask before running above this estimated cost. Default 1.
GITHUB_TOKEN, GH_TOKENbench GitHub sources. Falls back to gh auth token.

Tool-eval model drivers read the key variable set by apiKeyEnv in coding.sh.toml.

Output and updates

VariableWhat
NO_COLORNo color. Same as --no-color.
FORCE_COLORColor even when output is not a terminal.
CITreated as non-interactive: no prompts or spinners. Commands that would ask need --yes.
NO_UPDATE_NOTIFIER, CODING_SH_NO_UPDATE_CHECKTurn off the update check. Same as update.check = false.
CODING_SH_READ_BUDGET_MSHow long read commands (status, sessions, search, show, tree, usage, improve, harness audit, cache doctor, ...) and MCP tool calls ingest pending captures before answering from what is indexed (with a pending field in JSON and MCP results). Default 1500; --fresh waits for all. While a re-parse holds reparse.lock, these reads skip the ingest, leave the spool to the re-parse, and add "reparsing": true to pending.

Installer

Read by curl -fsSL https://coding.sh/install | sh.

VariableWhat
CODING_SH_YES=1Don't ask before running init.
CODING_SH_NO_INIT=1Install only.
CODING_SH_NO_UI=1Don't open the viewer.
CODING_SH_VERSIONInstall this release, e.g. v0.1.0.
CODING_SH_TARBALLInstall this tarball (path or URL) instead.
CODING_SH_SOURCEnpm or github. By default the installer uses npm when the registry has the version and the GitHub release tarball otherwise.

SDKs

Read by @codingdotsh/sdk and the Python coding_sh package when they send to coding.sh serve.

VariableWhat
CODING_SH_URLServer URL.
CODING_SH_INGEST_KEYIngest key.
CODING_SH_SERVICEService name; becomes the session's agent id.
CODING_SH_DISABLEDSend nothing.
CODING_SH_DEBUGLog what the SDK does.

Where agents keep their files

Adapters look in each agent's default location, or where these point: CLAUDE_CONFIG_DIR (Claude Code), CODEX_HOME (Codex), OPENCODE_CONFIG_DIR and OPENCODE_DB (OpenCode), CURSOR_CONFIG_DIR and CURSOR_DATA_DIR (Cursor), GEMINI_CLI_HOME (Gemini CLI), COPILOT_HOME (Copilot CLI), PI_CODING_AGENT_DIR and PI_CODING_AGENT_SESSION_DIR (Pi), GOOSE_PATH_ROOT (Goose), T3CODE_HOME (T3 Code), and XDG_CONFIG_HOME / XDG_DATA_HOME.