# Configuration
Precedence, highest first: CLI flags → environment (`OPENLATCH_*`) → `~/.openlatch/config.toml` → defaults.
The egress route is the one exception. It resolves **per key** rather than per block, and its ladder is longer: CLI flag → `OPENLATCH_*` → `[proxy]` in `config.toml` → the standard `https_proxy` family → OS discovery → direct. See [egress.md](egress.md).
`openlatch init` writes `config.toml` for you. Edit it by hand only for what the CLI has no command for.
## Environment variables
| Variable | Description | Default |
| -------- | ----------- | ------- |
| `OPENLATCH_DIR` | State directory: config, token, logs, outbox | `~/.openlatch` |
| `OPENLATCH_PORT` | Daemon port. `init` binds this exact port; unset, it probes 7443–7543. | `7443` |
| `OPENLATCH_API_URL` | Cloud API base URL | `https://app.openlatch.ai` |
| `OPENLATCH_API_KEY` | API key (credential chain: environment → keychain → encrypted file). `openlatch init` stores it in the keychain, or the encrypted file when no keychain is usable, because the daemon its supervisor starts does not inherit this variable. | — |
| `OPENLATCH_SKIP_KEYRING` | Treat the OS keychain as unavailable, so the env var wins | unset |
| `OPENLATCH_LOG` | `error` / `warn` / `info` / `debug` / `trace` | `info` |
| `OPENLATCH_PROXY` | Proxy URL for every outbound request. **The one channel that may carry a credential** (`http://user:pass@host:port`) — it is a per-process channel, unlike argv, and `init` strips the password into the OS credential store rather than persisting it. | unset |
| `OPENLATCH_NO_PROXY` | Additive bypass list. Loopback (`127.0.0.0/8`, `::1`, `localhost`) always bypasses and needs no entry. | unset |
| `OPENLATCH_PROXY_MODE` | `auto` / `manual` / `direct` | `auto` |
| `OPENLATCH_PROXY_AUTH` | `auto` / `none` / `basic` / `negotiate`. NTLM is deliberately absent. | `auto` |
| `OPENLATCH_PROXY_PAC_URL` | Explicit PAC URL. Refused on Linux with `OL-1225` — this client ships no JavaScript engine. | unset |
| `OPENLATCH_PROXY_SPN` | Kerberos SPN override | `HTTP/<proxy-host>` |
| `OPENLATCH_CA_BUNDLE` | PEM bundle merged on top of the OS trust store — the intercepting proxy's root | unset |
| `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY` / `ALL_PROXY` / `no_proxy` / `NO_PROXY` | Honoured below `[proxy]` in `config.toml`. **Lowercase wins** within a pair; on Unix, a pair that disagrees is a `doctor` warning. | unset |
| `OPENLATCH_MODEL_RELAY_ENABLED` | Model-relay listener. While on, the daemon points the agent at it — but only once a synthetic request proves the listener can reach the provider. Being wired disables Claude Code Remote Control — a Claude-Code-only consequence, since it follows from `ANTHROPIC_BASE_URL` being set. | `true` |
| `OPENLATCH_MODEL_RELAY_PORT` | Model relay port. A non-default port makes the instance isolated. | `7600` |
| `OPENLATCH_MODEL_RELAY_OWN_WIRING` | Let an isolated instance write its own agent config | derived from the port |
| `OPENLATCH_MODEL_RELAY_UPSTREAM` | Where the model relay forwards **`anthropic-messages`** traffic — this variable sets that one wire format, so it does not move Codex's route. Change it only for a local harness. To redirect another format, use the `[model_relay.upstream]` table in `config.toml` (e.g. `openai-responses`). | `https://api.anthropic.com` |
| `OPENLATCH_MODEL_RELAY_TRANSFORMS_ACT` | Whether an acting `prefix_reorder` rule may rewrite the forwarded request body. Off, the model relay measures and forwards the original bytes. | `false` |
| `OPENLATCH_POLICY_ENABLED` | Local policy evaluation. `false` is a complete off switch, not observe mode. | `true` |
| `OPENLATCH_INVENTORY_ENABLED` | Configuration-plane monitoring | `true` |
| `OPENLATCH_CLOUD_BATCH_MAX_EVENTS` | Events per ingest batch, clamped to `1..=100` | `50` |
| `OPENLATCH_CLOUD_BATCH_MAX_WAIT_MS` | Flush deadline, anchored to the first buffered event | `5000` |
| `OPENLATCH_CLOUD_OUTBOX_MAX_BYTES` | Outbox cap before drop-oldest (0 disables) | `104857600` |
| `OPENLATCH_CLOUD_FALLBACK_MAX_BYTES` | Fallback-log cap before drop-oldest (0 disables) | `52428800` |
| `OPENLATCH_CLOUD_ENABLED` | Parsed so existing configs keep loading, then ignored — cloud forwarding is not optional. Point `OPENLATCH_API_URL` elsewhere to change *where* events go. | ignored |
| `OPENLATCH_AUTO_UPDATE` | Background auto-update worker: the daemon checks the registry every 6 h and installs a release once no hook has run for 60 s (a critical release skips that wait). `true` or `1` turns it on. Any other value turns it off. Overrides `[update] auto_update` in `config.toml`. Off, `openlatch update` ends on a warning that names the setting that turns it back on. CI runners and `cargo install` binaries never auto-update. | `true` |
| `OPENLATCH_UPDATE_CHECK` | Startup version check. `false` or `0` turns it off. Same setting as `[update] check` in `config.toml`. | `true` |
| `OPENLATCH_NPM_REGISTRY` | Registry origin for `openlatch update` | `https://registry.npmjs.org` |
| `OPENLATCH_TELEMETRY_DISABLED` | `1` opts out of anonymous usage telemetry | unset |
| `DO_NOT_TRACK` | `1` opts out of anonymous usage telemetry. This is the cross-tool standard. Crash reports keep their own switch. | unset |
| `OPENLATCH_CRASH_REPORTING` | `0` (or `false`, `no`, `off`) stops crash reports. It works independently of the usage-telemetry opt-outs. | on |
| `OPENLATCH_POSTHOG_HOST` | Where usage telemetry and crash reports are sent. When unset or empty, it is the platform's own first-party proxy, so this traffic needs no destination beyond the platform's FQDN. Override it only to point a test harness at a mock: a development build — any debug build, and any release build with no project key baked in — sends usage telemetry and crash reports only when the resolved destination is a loopback IP address or a `localhost` / `*.localhost` host. | `{api_url}/ingest` |
## What the daemon writes into the agent's config
While the model relay is enabled, the daemon points **every detected agent** at the listener — after its bind succeeds and a synthetic round trip proves the listener can reach the provider — and removes the wiring on teardown. Nothing else writes these values, so an agent's config can never name a listener that does not exist.
Each agent's binding chooses the convention, and the supported agents use **different files**. Do not assume one config path per agent: Codex's provider table and Codex's hooks file are not the same file, and Cline gets one relay endpoint per provider rather than one base URL.
### Claude Code — environment keys in its settings
| Key | Value | Why |
| --- | ----- | --- |
| `ANTHROPIC_BASE_URL` | `http://127.0.0.1:<model-relay port>` | Routes the agent's provider traffic through the model relay |
| `ANTHROPIC_CUSTOM_HEADERS` | An install identifier, merged with whatever is already there | Attributes model calls to this install |
| `NO_PROXY` / `no_proxy` | `127.0.0.1,localhost`, merged into the existing list | Claude Code has no implicit loopback bypass; without it the agent would tunnel to its own laptop through the corporate proxy and get a `502` |
On teardown the wiring takes back exactly the `NO_PROXY` entries it recorded appending — a customer's own entries keep their place and spelling, and a variable the wiring created is removed once empty. The loopback entries carry no ownership marker in the file, so an install made before this was recorded leaves the list as it is.
### Codex CLI — a provider table in `config.toml`
A format-preserving `[model_providers.openlatch]` write on `$CODEX_HOME/config.toml` (default `~/.codex/config.toml`).
| Key | Value | Why |
| --- | ----- | --- |
| `name` | `OpenLatch model relay` | How the provider reads in Codex. **Mandatory** — omitting it makes Codex reject the entire config |
| `base_url` | `http://127.0.0.1:<model-relay port>` | Routes the agent's provider traffic through the model relay |
| `wire_api` | `responses` | Codex speaks the OpenAI Responses API and **rejects** `chat` |
| `requires_openai_auth` | `true` | Keeps the existing OpenAI/ChatGPT credential flowing to the upstream |
| `http_headers.x-openlatch-install-id` | An install identifier | Attributes model calls to this install |
Whatever the file named before us is recorded **before** the write commits, and restored on uninstall — that order is deliberate, so a crash between the two can never leave the customer pointed at us with no restoration record. A `[model_providers.openlatch]` table OpenLatch did not write is an error with a remedy, never a silent clobber.
### Cline — one relay endpoint per provider
Cline keeps a base URL per provider, and the VS Code extension reads it from its own state files, so OpenLatch writes **every configured provider** — one selected in act or plan mode, one with a URL of its own, or one with a `providers.json` entry — to its own loopback endpoint. Each endpoint forwards to exactly the origin it replaced, with the request path and query untouched. Nothing is set by hand.
| File | Read by | Keys |
| ---- | ------- | ---- |
| `$CLINE_DIR/data/globalState.json` | Cline's legacy build | `anthropicBaseUrl`, `openAiBaseUrl`, `ollamaBaseUrl`, `lmStudioBaseUrl`, `geminiBaseUrl`, `requestyBaseUrl`, `liteLlmBaseUrl`, `asksageApiUrl`, … |
| `$CLINE_DATA_DIR/globalState.json` | Cline's next build | The same keys. On a normal host both paths are `~/.cline/data/globalState.json`: one file, one endpoint per provider |
| `$CLINE_DATA_DIR/settings/providers.json` | Cline's next build only | `providers.<id>.settings.baseUrl`, for providers with no `globalState.json` key (DeepSeek, OpenRouter, xAI, Groq, Mistral, …) |
`secrets.json`, beside them, is never opened.
**What is written.** `http://127.0.0.1:<endpoint port>` followed by a path:
- **A URL of your own** (a gateway such as `https://gw.corp/anthropic`) keeps its path, and the endpoint forwards to its scheme, host and port. The provider's wire format tells the relay how to read a turn on a prefixed path.
- **No URL** writes the path both builds need to reach the provider's default host: nothing for Anthropic, Gemini, Ollama, LM Studio and LiteLLM; `/v1` for OpenAI and Requesty; `/server` for AskSage; the default host's own path for a `providers.json` provider.
The ports come from a block beside the model relay, `[model relay port + 1, model relay port + 32]`. A provider keeps its port, recorded in `$OPENLATCH_DIR/model-relay-endpoints.json` with the value it replaced and that value's exact form — absent, `null` or `""`.
**A running editor.** VS Code reads these files when Cline starts and writes its whole in-memory copy back on every settings save. A value OpenLatch writes while the editor runs is used from the editor's **next start**, and a save that puts the old URL back is re-applied within seconds. Until the editor is seen using an endpoint, `doctor` reports the provider as pending (`OL-RELAY-NEXTSTART`): nothing to do, VS Code picks it up when it next starts.
**When a provider counts as covered.** A request on its endpoint, or the editor saving a file that still names the endpoint. Two cases need a request: a `providers.json` provider, because the legacy build — the default — does not read that file, and a key your Cline organisation's remote configuration can override (`anthropicBaseUrl`, `openAiBaseUrl`, `liteLlmBaseUrl`, `awsBedrockEndpoint`) while that configuration is in force. Until then `doctor` reports them as not confirmed (`OL-RELAY-UNCOVERED`).
**Not carried** (`OL-RELAY-UNCOVERED`, calls go straight to the provider and keep working): Amazon Bedrock, whose requests are signed over the host name; a provider the running build reaches through no setting, such as DeepSeek or OpenRouter on the legacy build; providers whose host depends on another setting (a region or plan line); an unset Gemini URL while Vertex is selected; providers not reached over HTTP; and Cline's own account provider.
**Events.** Wiring rides the platform's tamper events, with the settings path hashed and the setting's **name** only, never its value: `relay_wiring_pending` when a provider is written and healed when the editor is seen using it; `relay_url_reverted` when a save removes the endpoint, healed when it is re-applied; and `relay_wiring_misconfigured` when an editor that loaded the endpoint uses the provider — named on Cline's own hooks — and no request reaches it within two minutes, healed by the first request.
**Stop and uninstall.** `openlatch stop`, a restart and a crash leave Cline pointed at its endpoints — an open editor still holds them — and the next start serves the same ports. While the daemon is down those providers' calls fail; `openlatch start` brings them back. `openlatch uninstall` restores every provider's original value in its original form, and so does switching the relay off (`openlatch system model-relay disable`) once the daemon restarts with it off. An isolated instance never writes Cline's files.