Skip to content

Runner environment variables

A runner installed with the one-line installer needs none of these: it is configured by pairing, and the service definition it writes carries no secrets. They are here for the cases where you run the program yourself.

Every variable on this page is read by the runner’s own process. None of them is handed to a coding agent, with one deliberate exception: with full setup on, CLAUDE_CONFIG_DIR and CODEX_HOME point runs at your own CLI folders.

VariableDefaultDoes
COMMANDCHAIN_URLhttp://localhost:7000Base URL of the CommandChain instance. Used as the default for connect, start, and every creds subcommand
COMMANDCHAIN_TENANT_IDnoneWorkspace this runner serves. Used by start only. connect learns it from pairing
COMMANDCHAIN_RUNNER_CREDENTIALnoneBootstrap credential used to register. Used by start only
COMMANDCHAIN_RUNNER_NAMEnoneDisplay name. Used by start only. connect defaults to <user>@<hostname>
COMMANDCHAIN_RUNNER_CREDENTIALS_FILE~/.commandchain/runner.jsonWhere the pairing credential is stored. Defaults to runner.json inside COMMANDCHAIN_RUNNER_STATE_DIR; setting this variable overrides that. Intended for scripts and test harnesses that need a throwaway file
COMMANDCHAIN_RUNNER_STATE_DIR~/.commandchainThe one directory for everything the runner keeps: the pairing credential (runner.json), workspaces (workspaces/), its own coding-agent sign-ins (agent-auth/), the agent setup choice (agent-setup.json) and its lock files. The installer passes it to the installed service. Every command on the machine that should see the same runner needs the same value
VariableDefaultDoes
COMMANDCHAIN_WORKSPACE_ROOT~/.commandchain/workspacesRoot directory for repository checkouts. Defaults to workspaces/ inside COMMANDCHAIN_RUNNER_STATE_DIR; setting this variable overrides that. One long-lived directory per repository
COMMANDCHAIN_POLL_INTERVAL_MS2000Heartbeat interval, in milliseconds
COMMANDCHAIN_CALLBACK_PORT0Port for the local callback API. 0 picks a free one
COMMANDCHAIN_TUNNEL_MAX_SESSIONS5Concurrent live-app tunnel sessions. A value that is not a positive integer is ignored with a warning
TENANT_CONCURRENCY_CAP5Runs this process will execute at once for one workspace
CHAT_CONCURRENCY3Concurrent huddle turns this process will handle at once
LOG_LEVELinfodebug when you are diagnosing something. The log is at ~/.local/state/commandchain-runner/ on Linux and ~/Library/Logs/commandchain-runner/ on macOS
COMMANDCHAIN_RUNNER_PLAINnoneSet to 1 to turn off the runner screen and write structured JSON logs instead, the same as --plain. Non-interactive terminals already get the JSON form without it

Each coding agent is found on PATH by default. Set these when the binary lives somewhere else.

VariableDefaultAdapter
COMMANDCHAIN_AGENT_PATHcommandchain-agentThe built-in agent (commandchain-agent-cli)
CLAUDE_PATHclaudeClaude Code (claude-code-cli)
CODEX_PATHcodexCodex (openai-codex-cli)
GEMINI_PATHgeminiGemini (gemini-cli)

Used only when the control plane has not resolved a model for the run. A model from the wrong provider family fails loudly rather than being quietly swapped.

VariableDefaultAdapter
COMMANDCHAIN_DEFAULT_MODELclaude-sonnet-4-6The built-in agent
CLAUDE_DEFAULT_MODELsonnetClaude Code
CODEX_DEFAULT_MODELgpt-5.4Codex
GEMINI_DEFAULT_MODELthe CLI’s own defaultGemini

There is no shared secret to configure, and no variable in this section.

While a run executes, the coding agent calls back into the runner with a short-lived attempt token the platform issued for that one attempt. The runner verifies that token with a key derived from its own pairing credential, and the platform signs it with the same key derived from the stored hash of that credential. Pairing already gave the runner the credential, so a runner installed with the one-liner has everything it needs to verify callbacks the moment pairing completes.

The platform’s own signing secret never leaves the platform. That is deliberate: a key that verifies attempt tokens for one runner must not be a key that mints tokens for every workspace, and a per-runner key cannot be.

A per-runner key is also not a key for every run. When the platform receives an attempt token, it checks the signature against the named runner’s key and then checks the attempt itself: the attempt has to have been opened for that runner and its run has to still be assigned to it, in the workspace the token names. A token that names someone else’s attempt, another workspace, or a capability the run was never granted is refused, whoever signed it.

Two consequences worth knowing:

  • A runner that is not paired refuses to start, with a message that says so. It has no credential, so it could not verify anything the agent will hold, and starting anyway would mean claiming work and then rejecting every callback, which reads as a platform fault.
  • Rotating a runner’s credential from the platform changes its key. A run already in progress keeps verifying its existing token until that token would have expired on its own, then the runner has to be restarted with the new credential. See pairing.

JWT_SECRET and DEV_JWT_SECRET are not read by the runner. Setting them has no effect.

creds push, and the c key on the runner screen, read your own coding CLI logins where those tools keep them, to upload one for runs on CommandChain’s managed servers. These variables move where they look, or supply a key directly.

VariableDefaultRead for
CLAUDE_CONFIG_DIR~/.claudeWhere Claude Code’s credential file is looked for. With full setup on, also the folder runs use
CODEX_HOME~/.codexWhere Codex’s auth.json is looked for
ANTHROPIC_API_KEY / CLAUDE_API_KEYnoneAn Anthropic key, when there is no Claude Code login on the machine
OPENAI_API_KEYnoneAn OpenAI key, read from Codex’s auth.json or the environment
GEMINI_API_KEY / GOOGLE_API_KEYnoneA Google key, when there is no Gemini login on the machine

An API key is preferred over a subscription login when both are present, because a key is revocable and avoids the provider’s terms-of-service question.

A coding agent does not inherit the runner’s environment. Each agent process is built from an allowlist, because a coding agent has a shell and network access, so anything in its environment is one request away from leaving the machine.

An agent child gets exactly three things:

  1. An execution baseline. PATH, HOME, USER, LOGNAME, SHELL, TMPDIR, LANG and LC_*, TZ, TERM, and certificate-bundle paths. Paths and locale, no credentials.
  2. The run’s selected environment. The project’s own values, released for that attempt only, injected into the process and never written into the workspace.
  3. That adapter’s own sign-in, and only its own. On a runner on your own machine, a copy of the runner’s own sign-in; on a managed run, the credential the platform released for that attempt. Either way it is staged into a synthetic home for that attempt. Provider keys in the runner’s own environment (ANTHROPIC_API_KEY for Claude, OPENAI_API_KEY for Codex, GEMINI_API_KEY for Gemini) are blanked in the child, so no adapter ever receives a key it was not signed in with and the run always executes as that sign-in.

Everything else is dropped: the runner’s own control-plane credential, its workspace identifiers, GITHUB_TOKEN, and any unrelated key you happen to have exported.