# 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_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. `openlatch update --check --json` is the manual probe. | `true` |
| `OPENLATCH_UPDATE_CHECK` | Startup version check | `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` |
The `NO_PROXY` entries are deliberately **left behind** on teardown: they carry no ownership marker, and a host's own loopback belongs in its own bypass list either way.
### 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.