The minute sweep checks persisted activation deadlines locally; only missing deadlines trigger metadata discovery. See the quota activation contract. Codex control-plane ids are defined in the dependency-free src/codex/control-plane-models.ts; src/router.ts routes these exact ids unchanged to canonical OpenAI, retaining the existing error when no OpenAI provider is enabled. They do not widen the public native roster.
package.json pins the shipped npm runtime (dependencies.bun) to 1.4.2 and the test runner (testRunnerBun) to 1.4.0; @types/bun remains unchanged. Runtime 1.4.2 fixes #6684's Windows Bun.fetch truncation after the first cleartext upstream body chunk.
The runner pin avoids 1.4.2's multi-file bun test --isolate preload reload segfault (load_preloads -> JSModuleLoader::loadModule -> JSPromise::status, address 0x10): #4064 introduced 1.4.2 on 2026-09-09 and #4821 reverted it on 2026-09-17.
#6713 run 37631558263 reproduces it on Linux 1/4, macOS 2/2 and Windows 9/9; single-file success does not prove the multi-file runner works.
.github/actions/setup-project-bun/action.yml defaults to role runtime (dependencies.bun); test-runner resolves testRunnerBun. Unknown roles and missing or invalid pins fail closed. .github/workflows/ci.yml verifies both resolved and installed versions in setup-action; tests/ci-workflows/ci-scope-gaps.test.ts guards job roles.
Test jobs, including single-file development-version validation, select test-runner. They also build dashboard fixtures; gates uploads a dashboard preview and checks release-helper syntax on that pin.
Release packaging, Docker, installed-artifact and service smoke jobs, desktop shell and widget preparation use runtime. Shipped binaries compile through process.execPath in scripts/build-standalone.ts and desktop/scripts/prepare-sidecar.ts on the runtime pin.
scripts/lib/test-runner-bun.ts selects the test pin for scripts/test.ts, scripts/release.ts preflight, scripts/test-layout/verify.ts, provider-option gates and GUI package tests via scripts/test-with-pinned-bun.ts: matching current Bun first, then a version-checked OCX_TEST_RUNNER_BUN, then PATH outside node_modules and ~/.bun/bin. Missing or mismatched binaries fail with install guidance, without downloading; package-script PATH shadowing cannot select the shipped runtime for tests. Rejoin only after an upstream fix and green unified lane=all proof; then remove the runner pin, resolver and CI role split.
src/router.ts attaches one frozen ResolvedModelPolicy to every RouteResult. Fast observation, persistence and cost provenance follow the response-tier authority contract; outbound Fast policy is unchanged. Policy/combo route spreads retain that object. Every initial, fallback, and recovery route is recaptured for the request's original inbound protocol before route-dependent normalization, and all adapter rebuilds consume its recorded adapter. A translated Chat or Anthropic replay therefore cannot inherit a Responses-only default. Credential, account, quota, health, cooldown, and observed transport evidence remain late and cannot widen a captured static limit.
Virtual models are the sole model-identity transition: the ordinary and compact paths preserve the selected public id in diagnostics, rewrite route.modelId to the upstream wire id, and atomically replace route.staticPolicy before adapter or capability decisions continue. Model aliases are resolved before the route result is built, so their policy is already keyed by the native wire id. src/router.ts applies bare blockedModelRedirects keys after this resolution; ordinary targets substitute one upstream model id on the selected provider/account, including slash-valued targets, while only targets explicitly naming a different configured provider reroute. Qualified source keys take precedence only for those cross-provider targets. A cross-provider redirect sets RouteResult.credentialDomainRewrite, which Responses and Chat header scoping treat like combo/policy routes, so a caller credential for the source route is never forwarded. src/cli/alias.ts consumes the JSON output flag before choosing its default list action. ocx alias --json and both flag positions around an explicit action preserve the selected read or mutation. Live selector hints obey the credential-scoped cache contract: a selection change cannot reuse the previous credential's roster to choose an alias target. Passive OAuth observation neither refreshes credentials nor repairs their storage. Cross-provider redirects require an enabled destination with a resolved key, usable stored OAuth account, or local/keyless auth (never source-caller forward auth), and share one visited set and five-edge budget across alias, provider, combo and policy resolution; every policy redirect must end at an eligible declared candidate, including same-provider substitutions and candidate fallback retries, and pinned account selectors reject provider changes. Decision traces name the final provider/model while policy candidates retain selection evidence and combos retain their route kind and redirect reason.
Routed Meta Muse requests use the registry-owned Muse effort and header contract; max reaches the provider through the existing reasoning mapper.
Native result continuations and function-result injection follow the mode-specific result and control contract; this surface does not infer upstream support or alter its defaults.
Native steering follows the shared WebSocket contract; this surface's defaults remain unchanged.
Responses admission and finalization are composed through the core module ownership. Kiro's optional account-load admission is process-local and request-owned; its slot ends with the response body or cancellation. Other providers retain their admission path. Cancellation after a slot grant and before the request resumes releases the slot and returns cancellation without dispatch.
Catalog HTTP acquisition follows the proxy-routing contract. CLI model lists expose shared catalog estimates as price; models price retains the saved override in cost and adds effectiveCost. Explicit zero overrides win; unknown prices remain null, and automatic defaults are never persisted to modelCosts. Covered by tests/cli/cli-models.test.ts and tests/cli/cli-models-price.test.ts. Ordinary model discovery, startup sync, and explicit sync apply the new-arrival catalog contract before publishing rows. The scoped persisted decision also updates the live config merge baseline, so subsequent visibility writes preserve manual re-enablement. A file-loaded inventory that already differs from disk can publish HTTP rows through a detached policy projection without adding unsaved live disables; retained catalog writes still refuse stale inventory, and its load provenance follows the config contract.
OAuth refresh coordination follows the refresh-lock identity contract: a fresh unreadable lock remains held, and release requires matching descriptor identity. A failed path-identity probe preserves the refresh callback outcome. Cooperating lock metadata changes serialize through the existing SQLite mutation transaction; release keeps the descriptor open through identity comparison and any unlink, then closes it. Failed metadata writes remove only a matching owned path after successful coordination; unknown identity, failed probes or unavailable coordination retain the path for stale recovery. Async refresh work holds no metadata transaction.
The configuration-only plaintext V2 contract is scoped to canonical ChatGPT Responses forwarding; other source-area behavior described here is unchanged. Cursor's localized native-shell names follow the routing-commentary guard contract.
Chat request serialization owns chronological instruction ordering and the developer wire role; it requires no runtime lifecycle change, and its one configuration option is a per-provider role opt-out.
Shared parsing and streaming follow the request-copy and stream-buffer accounting contracts. Response-attached WebSocket telemetry follows the stage record identity contract.
src/claude/outbound.ts starts Anthropic semantic framing lazily. When response.created or
response.in_progress reports numeric input usage before the first content event, message_start
uses that confirmed value through the normal Anthropic cache-token transform. Without an early
measurement it emits the required zero snapshot without estimating or delaying content. The terminal
message_delta.usage remains cumulative and is always derived from the terminal response usage;
this wire projection does not change the usage ledger.
The CLI management contract owns terminal-safe human diagnostics, structured-output fidelity and capability argument failures. Its data-key and observation contract also owns explicit stdin cancellation, credential separation and bounded follow lifetimes.
ocx resolve (src/cli/resolve.ts) is the machine surface a desktop shell asks instead of resolving the config home, the port, and liveness itself: the home comes from src/config/paths.ts, the effective port is the live listener's when the identity-checked findLiveProxy answers and the configured config.port ?? 10100 otherwise, and the liveness verdict is that same module's output (pid, runtime-versus-config provenance, version, role). Config reads go through readConfigDiagnostics, not loadConfig: a missing file is defaults, but an invalid file exits 1 instead of being repaired to defaults, because a defaulted port is a guess the caller must refuse. Liveness is three-valued: when findLiveProxy returns null, resolve re-asks the endpoints with endpointsToProve and everyEndpointProvenDown, using the in-process probeEndpointLiveness from src/server/proxy-liveness.ts. Only unanimous definitive "dead" becomes absent-proven; a timeout or reset stays unknown, exits 1 and never authorises a start. The transient exclusive-bind fallback in src/update/proxy-liveness-probe.mjs belongs only to the npm/Bun updater. Resolve stays in-process because a compiled standalone binary cannot run a JavaScript probe with execPath -e. Discovery borrows START_OWNERSHIP_LIVENESS, the start path's ownership budget — the verdict feeds the shell's launch decision, so the cost of a false "nobody listening" is the duplicate proxy (#5004). The verb is in skipsCodexShimAutoRestore, so a read-only lookup never triggers a shim repair. Arguments are pre-parsed in src/cli/root.ts and exit 64 before any preflight side effect, the same ordering ocx ready obeys. ocx-resolve/1 also reports ownership (none, owned, or unknown) and takeover (supported with protocol version, minimum CLI version and compatibility token, or blocked with a reason). A known liveness verdict still exits 0 when ownership is unknown, but takeover is blocked. An unreadable second service-state read blocks takeover instead of becoming an absent registration. src/service/managing-cli.ts observes the registered and selected PATH CLIs, including Windows PATHEXT order and file validation, before compatibility is offered. A --version probe is retried once on Windows — one transient failure must not read as an unobservable CLI — and a Windows .CMD/.BAT shim is invoked through a single verbatim-quoted cmd /c line, so a spaced executable path and spaced arguments both survive cmd's re-parse. src/cli/route-policy.ts distinguishes a missing profile (exit 4) from malformed show arguments (exit 2); an upstream failure propagates through the management error mapping.
ocx stop --json is a reporting layer over the unchanged stop path. src/cli/index.ts threads a StopRunRecord through the existing receipt, drain, respawn-verification and restore flow, and src/cli/stop-report.ts maps the recorded facts plus the signals that already pick the exit code into one versioned document (schema: "ocx-stop/1"). With --json the human lines print on stderr and stdout carries only that document; exit codes 0/1/79/80 cross the process boundary unchanged. The desktop's opt-in guarded stop adds exact approved PID, endpoint, home, CLI version and compatibility-token expectations; plain ocx stop retains its ordinary behavior. Under the ownership mutation lease, src/cli/stop-approval.ts re-resolves them and src/service/guarded-manager-target.ts binds any installed manager's process tree to the approved PID before a stop action. A pre-action mismatch returns approval-changed without stopping, and the summary carries detail naming the single guard fact that failed — live PID/port/hostname/CLI drift, an unverifiable ownership claim, a blocked takeover, or the compatibility fingerprint — so a transient re-probe can be told from genuine drift in the caller's log. Immediately before any manager stop or direct PID signal, the CLI rechecks the same manager identity or proven absence; a change also returns approval-changed. A non-OpenCodex process replacing the OS job in the remaining instant before the manager command is a residual same-user risk outside the ownership lease. The guarded path stops the bound manager, or the approved PID directly when managers are proven absent, then uses a five-second shared deadline to verify PID exit, port availability and definitive endpoint absence. Only after settlement does a read-only manager probe require launchd not-loaded in both domains or systemd inactive with MainPID=0; the state is checked again before a success summary. Timeout, active or unreadable manager state returns terminal manager-still-active, even if the endpoint briefly refuses. Windows managers bind through src/service/windows-guarded-manager.ts: a Task Scheduler backend must prove a recognized OpenCodex registration against the bounded current-user SID/name lookup, a running task, and a wrapper ancestor carrying this home's canonical paths — presence and running state are read root-folder-scoped because every /tn operation resolves a bare name there and nowhere else, so a same-named task inside a task folder is a different registration neither the probe nor the stop can reach; the registered definition itself is read only on the running path, and a state or registration read that cannot answer re-probes presence so a task deleted between probes — which can respawn nothing — reads absent while a merely unreadable one stays unknown; an unreadable current identity leaves the manager unknown; a WinSW backend must prove this installation's binary and SCM service PID ancestry; either marks childNeedsSeparateStop, so the approved PID receives its own graceful signal only after a successful manager stop because schtasks /end does not reliably cascade to the wrapper's child (#764); a failed or unknown manager stop leaves the child untouched. Post-stop Windows inactivity means the task not running, no surviving wrapper process, and no started WinSW — never mere registration presence; a running-state read that cannot answer re-probes presence the same way, so a task deleted between probes reports inactive while a persisting one stays unknown. Every Windows manager command a verdict or stop depends on — schtasks, sc.exe, WinSW verbs — runs under a bounded execFileSync timeout so a wedged Task Scheduler or Service Control Manager cannot hang the guarded stop; a killed command classifies through the same fail-closed paths a nonzero exit would (the interactive install /p password prompt is the one deliberate exception). A wscript.exe, cscript.exe or cmd.exe whose command line CIM cannot read makes the state unprovable only when it is tied to the proxy: pre-stop, as an ancestor of the approved PID; post-stop, as the former bound manager process or a descendant of it. Unrelated unreadable shells, which a non-admin query returns for other users' and elevated processes, are not evidence either way. An unprovable Windows manager still blocks guarded takeover. The token checks snapshot consistency, not whether a person approved the desktop prompt. src/cli/stop-restore.ts owns the shared Codex/Grok restoration and its history-only, history-deferred, and other failure categories.
src/cli/account-main.ts emits one JSON object to stdout when ocx account main reauth --device --no-wait --json succeeds. The human-readable follow up: line is emitted only without --json; flowId remains available for status polling. tests/cli/cli-native-profile.test.ts parses the complete captured stdout and preserves coverage of the human follow-up.
ocx system codex-restart requests a full Codex desktop-app restart and app-server restarts through the management endpoint. src/cli/capabilities.ts names that scope and warns that unsaved composer drafts, model-picker selections, and pending approval prompts may be discarded. src/cli/system-command.ts repeats that concrete state-loss warning both when confirmation is missing and after a confirmed human-readable request; the unconfirmed path sends no restart request. --json preserves the complete server result, including skipped or refused desktop outcomes. An armed test process never reaches the real desktop app. When the test preload's OCX_TEST_HOME_GUARD=1 is set and the caller injected no execFile, restartCodexDesktopApp in src/codex/desktop-app-restart.ts returns the skipped reason test_environment before discovery or signalling, and handleDesktopAppRestart in src/cli/restart-scope.ts reports that skip. The flag, not NODE_ENV, decides, so a real NODE_ENV=test ocx ... still restarts the app; adapter tests that inject execFile still exercise the full path. tests/clients/desktop-app-restart.test.ts covers the skip.
After a CLI catalog/cache write, advisory restart guidance compares each running Codex app-server's
start time with the written catalog mtime. It reports only processes proven stale; a fresh or
unreadable observation does not claim that another restart is required. Explicit
--restart-codex and --restart-app-server-only retain their operator-consent semantics and act on
verified matching processes regardless of the advisory freshness result. Read-only client diagnostics in src/codex/native-profile-processes.ts prefer full executable paths over inferred argv0; macOS comm and args are read separately and joined by PID, so unquoted Codex Framework helper paths do not become CLI clients. The initial split Codex.app framework-path token pair is also rejected when only a process basename is available; later path arguments do not override a client match. Quoted immediate script entrypoints remain supported for known interpreters. This classification does not change the app-server termination selector.
Decision record: ADR-0097
When hub management ingress is enabled, src/cli/dispatch.ts opens the dashboard on the literal IPv4 loopback address and configured ingress port, matching the listener in src/server/index.ts. Other dashboard address selection is unchanged.
src/codex/desktop-app/windows.ts discovers the installed package and limits process ownership to the current Windows user.
Its PowerShell prefilter normalizes both the install root and candidate executable from / to \ before a case-insensitive prefix comparison.
The adapter then folds both slash forms onto the host separator before calling isUnderRoot() in src/codex/desktop-app/types.ts.
That shared lexical boundary check rejects sibling prefixes such as OpenAI.Codex-evil; Windows path folding stays in the Windows adapter, so a POSIX backslash remains a filename character.
The prefilter is only an optimization, not final process-membership authority.
tests/clients/desktop-app-restart.test.ts covers both mixed-slash directions through the adapter and runs the real PowerShell filter against synthetic CIM rows on Windows.
tests/clients/desktop-app-restart-posix.test.ts keeps the POSIX separator contract covered; uid-dependent macOS/Linux cases skip on Windows.
src/cli/codex-cli-update.ts dispatches the opt-in Windows x64 attest operation to
src/codex/cli-installation-identity.ts. With no options, src/codex/cli-installation-targets.ts
derives the four inputs from the proof-bound launcher snapshot: the configured candidate or
the first codex on the captured PATH, an OpenCodex wrapper resolving to its renamed npm
backing, the npm prefix layout, and the Node/npm toolchain beside the resolved node.exe.
Configured values containing a path separator must be drive-absolute; otherwise derivation
refuses with candidate_unavailable instead of substituting a different PATH candidate. Bare
command names and the unset default continue to resolve only through the captured PATH.
Discovery probes candidates through the same local-volume, reparse-refusing held-handle reader as final observation, so captured PATH entries cannot trigger network filesystem I/O.
It never reads ambient state. Four explicit absolute paths remain accepted as an all-or-none override.
Only the standard npm command shim or direct Codex package entry is accepted. The native reader in
src/codex/windows-installation-files.ts holds ancestor/file handles for bounded reads and
rejects reparse points, conflicting writers and unsupported paths/platforms. Its path-free
report binds file identities and bytes to this observation, not a durable update permission.
installationIdentityObserved can be true; selectionAttested, managed and applyAllowed
remain false. Supplied Node identity does not prove launcher selection, effective npm config,
past installer identity or tool authenticity. No package-registry request, installation,
config write or process control occurs. Existing Windows check retains zero candidate/config
filesystem I/O. A reported refusal can exit 0; consumers inspect status.
The local account CLI and pool credential resolver share the Orca source-owned import contract: importing does not perform OAuth, and runtime credential resolution rereads the owned source.
| Path | Responsibility |
|---|---|
bin/ocx.mjs |
Published npm bin entry (Node shim). Resolves the bundled or explicit Bun binary before project dotenv can load, stamps its runtime provenance plus a proof-bound Anthropic parent-env snapshot, lazy-runs bun/install.js if only the placeholder stub is present, then execs src/cli/index.ts under Bun. Lets npm install -g work without a separately-installed Bun. The exact system codex-cli-update inspection namespace skips both boot repair and lazy Bun installation; missing runtime support fails closed instead of mutating state. |
src/lib/bun-runtime.ts |
Bundled-Bun resolution: isRealBunBinary() (size gate vs the ~450-byte placeholder stub), bundledBunPath(), and durableBunPath() (path baked into service/shim artifacts). Durable selection accepts only the source/path pair already stamped for the running executable; it never re-reads a project-dotenv OPENCODEX_BUN_PATH. |
src/lib/plain-data.ts |
Detached copies for a consumer that must not observe later edits. Descriptor-based reads, including array elements, so an accessor is refused rather than invoked; refuses cycles, functions, class instances and anything else JSON could not have produced, and returns a copy-or-refusal union rather than degrading silently. Symbol-keyed process bookkeeping is skipped. |
src/cli/index.ts |
ocx / opencodex CLI. Lifecycle: init, start, stop, restart, status, sync, restore/eject, gui, service, update. restart refuses an in-place restart requested by a CLI whose version differs from the attested /healthz version; a newer CLI may use the guarded standalone update path described below, because an in-place replacement respawns from the live installation; placeholder versions (unknown/0.0.0) stay incomparable and keep the restart path. Configuration: provider, account, models, combo/route, access, integrations, v2. Client launchers: Claude, OpenCode, MiniMax Code, and MiniMax CLI text. The MMX launcher owns a child-lifetime loopback path bridge from the client's hard-coded /anthropic/v1/messages path to the canonical /v1/messages data plane; the server does not expose an extra auth surface. Diagnostics: doctor, debug, observe, health. Windows adds tray. Hidden __update-badge prints the read-only package badge JSON without refresh or shim repair for the npm tray. The full top-level reference is rendered by src/cli/help.ts; this table names the groups, not every verb. After help/version and unknown-root early exits, recognized commands run the bounded best-effort Codex-shim auto-restore policy before dispatch. status, doctor, and codex-shim status skip shim auto-repair; system codex-cli-update and command-local messaging suppress it for their whole namespaces, including malformed invocations. Keeps the #!/usr/bin/env bun shebang for from-source dev (bun run src/cli/index.ts). src/cli/init.ts validates an explicit setup port as a decimal integer from 1 through 65535 before publishing the initial configuration. Only an empty answer selects 10100; malformed input is reported and the port question is asked again, while EOF or SIGINT still cancels without saving. |
src/cli/root.ts, src/cli/help.ts, src/cli/help-navigation.ts |
CLI head and compact/full help; see head and help navigation. |
src/cli/help-catalog.ts, src/cli/help-models-context.ts, src/cli/help-recovery.ts |
Pure help resolution, contextual recovery and output sinks; see head and help navigation. |
src/server/index.ts |
Bun server entrypoint: startServer, /v1/responses HTTP + WebSocket routing (compact handled before generic Responses), exact POST /v1/images/generations and POST /v1/images/edits routing, /v1/models, the Anthropic-shaped /v1/messages and OpenAI-shaped /v1/chat/completions compatibility surfaces, the Live/Realtime surface, the hosted-search relay, artifact serving, /healthz, the /api/* auth gate, the /v1/* JSON 404 guard, GUI fallback, the opt-in loopback-only hub-management listener, and facade re-exports for split server modules. The route table itself is built by src/server/index/serve-options.ts; this entry file owns the listener and the startup transaction. |
src/server/images.ts |
Standalone Images data plane: default OpenAI or explicit custom-provider selection, Codex account affinity, bounded opaque request relay, single-attempt upstream fetch, pool health recording, and safe response/cancellation relay. |
src/server/audio-transcriptions.ts |
Standalone multipart transcription; audio-specific key admission, bounded upload/response, stored OpenAI credential resolution and lease-bound cancellation. See audio contracts. |
src/server/audio-live.ts, src/server/audio-dictation.ts |
External voice/dictation orchestration using the existing bounded socket relay, server-owned credentials, cancellation and opaque call ownership. See streaming audio. |
src/config.ts, src/config/persisted-mutation.ts |
Persisted ~/.opencodex/config.json surface: the facade keeps load/save/initialize entry points and re-exports the schema-valid mutation callback, types, and one-shot test seam; the leaf owns its bounded rebase under the shared lock. Schema lives in src/config/schema/ (config-schema.ts, leaf-validators.ts), defaults in src/config/proxy-env.ts, and replace-path persistence in src/config/persist-unlocked.ts. |
src/config/paths.ts |
Resolves OPENCODEX_HOME, config.json, and owner-only directory hardening. |
src/config/atomic-write.ts |
Shared synchronous/asynchronous temp-harden-rename writer and residual-temp failure contract. The temp is ACL-hardened before it holds a byte and again before the rename, both required: true; the second call is a memo hit rather than a second icacls sequence because the writer re-asserts descriptor/path identity after the content write and re-attributes the harden through reattributeHardenedSecretPath. Windows takes no chmod on that path — it sets the read-only attribute, not the DACL, and its ChangeTime bump is what used to retire the memo. |
src/config/process-state.ts |
Owns ocx.pid, runtime-port.json, cheap liveness, full command-line identity verification, and snapshot-guarded cleanup. A transient Windows WMIC/PowerShell command-line probe failure gets one bounded retry before a PID reads as unverified. |
src/config/admitted-identity.ts |
Which configuration a derived artifact was built from. Detaches the resident configuration as plain data so one pass cannot gather under one state and project under another, and records the complete structure beside the configuration file's bytes. Refuses an accessor, a cycle, a value JSON could not produce, an unreadable file and a file the loader would have had to salvage; a callable providers[name].fetch is the one non-data field, held and compared by reference, while a written one is ordinary data on both sides, as the outbound transport also reads it. It does NOT require the resident configuration to equal the file: the proxy routes by what it holds, and live reconciliation retains live changes and the active listener binding on purpose. Evidence stays in a module WeakMap, never on the config and never in a response. |
src/server/ports.ts |
Owns bind availability and ephemeral-port selection. Temporary probes dispose accepted peers and wait for listener close before reporting success. |
src/cli/status.ts / src/cli/status-probes.ts |
Status snapshot assembly and the shared read-only health/stale-process probes used by status and doctor. Probe evidence keeps recorded-port choice, before/after snapshots and per-call timer cleanup together. |
src/cli/doctor.ts |
Read-only environment diagnostics. Sections print through console.log; each is a collect* helper above runDoctor so it is testable without the command. Only a FAIL-level condition records a doctor failure — a degraded-but-working install must not break a green pipeline. collectDefaultModelExposure compares Codex's root model pin against the exposed set, which it READS rather than recomputes: the running proxy's /v1/models through the byte-capped direct-local transport when one answers, otherwise the on-disk catalog's visibility: "list" slugs. It reports exposed, not exposed, or undeterminable, and never the second when it could not read either surface. |
src/router.ts |
Provider/model selection before adapter dispatch. Policy execution and ordinary management dry-run share effective-provider capability evidence; unresolved, missing, and disabled providers are excluded before scoring. |
src/providers/api-key-selection-capture.ts |
Pure request-owned snapshot of the configured key entry, reference, and revision. The router and stateful selection module share this leaf with type-only dependencies; api-key-selection.ts retains the compatibility export and owns persisted selection changes and route resolution. |
src/types.ts |
Shared config, parsed request, adapter, and event types. |
src/reasoning-effort.ts |
Codex reasoning-level definitions (low/medium/high/xhigh), per-model effort mapping, and catalog effort sanitization. |
src/cli/tray-proxy.ts |
Proxy restart coordination. Replacement re-observation captures one clock value for both its remaining budget and absolute end, so the observation window never extends the caller deadline. CLI proxy update restart: src/cli/system-restart-client.ts preserves a private newer-CLI candidate with its original runtime record and physical home snapshot. src/cli/update-restart.ts accepts only known versions and positively classified standalone POSIX targets: PID-1 parent, no installed/active manager, no ownership record, no client/sibling role. It holds the existing ownership mutation lease through stop and spawn and revalidates the captured target before stop. src/cli/update-restart-transport.ts proves identity and sends stop on one direct TCP connection, refuses reconnects and requires complete successful teardown acknowledgement. It never signals a PID or reclaims a port. Confirmed predecessor exit, runtime-record removal, port availability and definitive endpoint absence precede the one launch. The executable that launch spawns must pass the shared REAL_BUN_MIN_BYTES gate: an incomplete runtime refuses before any stop byte, and after a confirmed stop the transaction waits for it within the deadline, then rechecks deadline and physical home synchronously before spawning. src/cli/update-restart-child.ts strips inherited lease delegation, acquires its own lease before CLI preflight and checks the frozen physical homes, ownership revision, CLI version, endpoint and deadline before startup, each bind and publication. It keeps custody through rollback; its deadline ends an unfinished child. src/cli/update-restart-home.ts owns physical-home and unclaimed-state checks. Success requires that launched child PID, fresh attestation and the exact known version; all incomplete outcomes terminate without generic restart recovery. Supervised, Windows and uncertain cases refuse this update path. |
src/codex/shim.ts |
Codex autostart facade: dispatches Unix private PATH overlays to src/codex/shim-overlay.ts and retains Windows in-place script wrapping. src/codex/shim-migration.ts owns explicit legacy Unix restoration. Legacy Unix shim migration rejects symlink, foreign-owned, and group/world-writable state files. The writable-mode refusal prints a shell-quoted chmod 600 hint; it clears only that mode refusal, leaving file-type and ownership checks intact. tests/codex-integration/codex-shim-migration.test.ts executes the hint through /bin/sh with a space and apostrophe in the state path, then verifies mode 0600 and schema-2 migration. src/codex/shim-diagnostics.ts owns diagnosis/status, distinguishes runnable from active overlays and states the unhealthy verdict for a damaged in-place (Windows) wrapper. src/codex/shim-templates.ts uses src/lib/self-launch-argv.ts to invoke standalone executables with ensure directly in Unix, CMD and PowerShell wrappers, without a virtual source entrypoint; management subcommands bypass startup even after value-taking global flags. Discovery resolves fnm’s temporary fnm_multishells directory to the durable installation directory through src/codex/shim-path-resolution.ts, taking the directory realpath while retaining the command basename; unresolved temporary entries refuse installation. The injected path flavour governs join and separator. The shared PATH scanner follows npm/fnm symlinks and POSIX target execute access, skips non-executable and special-file entries, and reads only the first 16 KiB of regular files through a descriptor with nonblocking POSIX open and a post-open type check. An unreadable executable still shadows later commands. Overlay activation requires the selected executable to identify the recorded private wrapper or the same file through an alias; another marker-bearing shim does not prove activation. Shell functions/aliases and empty PATH components are outside this scanner’s guarantee. src/cli/codex-shim-readiness.ts retains connect’s advisory exit policy. tests/codex-integration/codex-shim-path-readiness.test.ts covers POSIX shell lookup parity, FIFO replacement, symlinks and bounded reads. Windows retains PATHEXT lookup without POSIX execute-access checks. |
src/service.ts |
OS service manager (macOS launchd, Linux systemd, Windows schtasks and native WinSW): always-on proxy with crash restart. The scheduler wrapper logs transactional-backup recovery without interpolating backup names. Facade over the src/service/ leaves — src/service/launchd.ts, src/service/systemd.ts, src/service/windows-ops.ts, src/service/windows-scheduler.ts, src/service/windows-taskxml.ts, src/service/state.ts, src/service/guards.ts, src/service/health.ts, src/service/repair.ts, src/service/orchestration.ts, src/service/diagnostics.ts, src/service/cli.ts. Service definitions filter shell-local multishell PATH entries, including WinSW XML generated during install and repair, and recorded temporary launchers are replaced during repair; changed launchd definitions are reloaded. Codex-home ownership accepts either the recorded path or the same existing physical directory so path aliases remain compatible across upgrades. Elevated Task Scheduler repair stages bounded payloads; the unelevated launcher pins every namespace ancestor and payload with non-reparse handles that deny write/delete sharing on the payload and delete sharing on each ancestor until UAC processing exits. On Windows, src/service/windows-process-priority.ts raises the process that binds the proxy to ABOVE_NORMAL just before startServer (best-effort; OCX_DISABLE_PRIORITY_BOOST=1 opts out), because the NORMAL task priority (<Priority>4</Priority>) still let a saturated host hold /healthz past the 750 ms CLI liveness ceiling; spawned children fall back to NORMAL since Windows does not inherit ABOVE_NORMAL. The Task Scheduler batch wrapper (src/service/windows-taskxml.ts) checks bundled Bun against REAL_BUN_MIN_BYTES before each launch: npm's placeholder, or a file that vanished before its size was read, is logged and re-checked every five seconds instead of executed, while a Bun path missing at the exist check keeps the backup-restore then exit-3 path (wrapper contract). |
src/cli/provider.ts accepts the Google-only --google-tool-schema-policy creation flag and rejects
an unknown value or non-Google effective adapter before persistence. The persisted field and default
are owned by the config contract.
The src/ root stays thin: process entry (src/cli.ts, src/index.ts), shared config/types,
router, bridge, service manager, reasoning-effort definitions, and the stall-timeout budget live
there. Feature code is grouped by responsibility:
| Group | Directories |
|---|---|
| Data plane | src/adapters/, src/responses/, src/chat/, src/claude/, src/grok/, src/images/, src/vision/, src/web-search/ |
| Codex integration | src/codex/, src/combos/, src/providers/, src/oauth/ |
| Surfaces | src/server/, src/cli/, src/tray/, src/github/ |
| Evidence and contracts | src/compatibility/, src/lab/ |
| Support | src/lib/, src/storage/, src/usage/, src/update/ (package refresh; desktop-badge.ts holds bounded process-local display state, never install authority), src/generated/ |
src/generated/ is committed build output, not hand-edited; scripts/generate-model-metadata.ts derives kimi-responses → Moonshot metadata from the registry's jawcodeBundle while keeping its provider row distinct.
src/server/ is split by responsibility: index.ts owns the listener and the startup transaction
while index/serve-options.ts owns route ordering; responses.ts and responses/core.ts compose
Responses handling from the owners inventoried in Responses transport,
and responses/compact.ts owns compaction; images.ts owns the standalone Images relay;
responses/codex-auth-error.ts owns the shared Responses/compact Codex auth-context HTTP mapping.
Model entitlement denial is a 400 request error and temporary exhaustion of every model-capable
account is a retryable 429; neither is reported as an invalid API key. Images, Live, and Search
reuse that model-availability mapping while retaining their existing credential handling. Account
selection, credential materialization, logging, and transport stay in their existing handlers;
management-api.ts owns /api/*;
lifecycle.ts, request-log.ts, relay.ts (incl. the shared createSseInspector SSE inspection
factory), relay-eager.ts (#314 gated eager bounded passthrough relay), memory-watchdog.ts
(warn-only RSS sampler), management/system-routes.ts (/api/system/*), and auth-cors.ts own
server infrastructure (src/lib/bun-stream-caps.ts owns the Bun stream-capability gate); and
static GUI, WebSocket bridge, port/liveness, decompression, and adapter-resolution helpers live in
their own files.
Startup catalog sync and native restore apply the retired-native policy.
src/server/background-lifecycle.ts registers low-quota protection for each live server, flushes pending saves before releasing its owner, binds its own event ledger to management requests, and unregisters it on failed startup or stop; with no pending save, owner release remains synchronous. The protection writer loads config persistence only when a threshold schedules a save; see configuration. Codex quota processing has shared and Reserve scopes; retired model evidence is suppressed as
described in OpenAI quota ownership.
ocx start refuses a duplicate PID, starts the proxy, writes ~/.opencodex/ocx.pid and
runtime-port.json through src/config/process-state.ts, syncs Codex config/catalog, then serves
until shutdown. Normal shutdown restores native Codex; a sibling instance beside a live proxy (Codex home) syncs and restores nothing, and ocx stop of a runtime whose record carries siblingOfPort skips the shared teardown. src/cli/index.ts resolves same-home ownership, then checks shared client hints through the cross-home owner helper, then reconciles the journal only when no sibling is marked. Service mode sets
OCX_SERVICE=1, so managed restarts do not repeatedly restore/reinject; explicit service stop and
uninstall still restore. src/service/cli.ts removes the service token on uninstall only when persisted client state is disconnected and no pending connect marker owns the newly issued key. src/client/connect.ts publishes that fingerprint marker before the key, then clears it with the connection commit or rollback under the client lifecycle and config mutation locks. Connected, invalid, or mismatched client state retains an existing token. A valid pending marker retains only its matching fingerprint; an older marker does not own a replacement service key. An absent token is reported as absent; unsafe, malformed, or unreadable markers and lock, state-read, or deletion failures leave cleanup unverified.
src/cli/tray-proxy.ts coordinates ocx restart and tray restart. A second start requires proof the prior attempt never launched or its child exited, plus confirmed absence under a fresh bounded discovery window; a missing health response alone is insufficient. After an accepted restart, polling continues through uncertain observations until its deadline and requires a new runtime PID on the same port. If the old proxy then disappears, explicit recovery starts it regardless of the Codex autostart preference; a skipped recovery fails rather than reporting success.
The package-tree integrity fence for live package replacement follows the update transaction contract. After client journal reconciliation, ocx ensure in src/cli/dispatch.ts treats validated connected-client state as a successful no-op without local startup or a remote health probe; invalid or mismatched state still fails, and disconnected state keeps local ensure. tests/cli/cli-ensure-client-state.test.ts pins this dispatch contract so the Codex shim does not warn merely because its proxy is delegated to a hub. Startup journal reconciliation, including replacement after restart/drain, follows the Codex-home recovery contract: already-original recovery is silent, incomplete recovery retains its journal and warns without claiming restoration, and only complete recovery that rewrites or removes an artifact reports a restore; restart admission, drain deadlines and replacement ownership are unchanged.
A busy preferred port is never resolved by starting somewhere else. Both questions a start asks
about an existing proxy — the pre-bind owner check and the port-is-busy check in src/cli/index.ts
— are identity probes with a retry budget, because a start that answers "nobody is there" on one
lost probe deletes this home's pid record and then binds a second listener that takes over the
records and re-points Codex at itself. probePortOwner in src/server/proxy-liveness.ts asks the
busy port directly, on both loopback families, independent of the pid and runtime records; the
outcome is the pure decision decideBusyPreferredPort in src/cli/dispatch.ts. An opencodex
holder is refused with the same message the owner check prints (intentional stay-out under
OCX_SERVICE=1, using the Windows wrapper protocol), and a holder that does not identify as opencodex
is reported as such rather than called foreign, because an identity probe cannot distinguish a
foreign server from an unreachable one. An explicit --port still never hops — it waits for the
pin through src/server/port-reclaim.ts — and a configured port: 0 still means "ask the OS". A restart replacement that finds its own draining parent waits for it instead (restart handoff). During drain, src/server/index.ts answers new data-plane work with HTTP 503 JSON server_restarting, Retry-After: 5, and receiving-listener CORS; the same handoff section owns that response contract.
Every startServer invocation acquires the src/lib/spend-ledger-owner.ts SQLite writer lease
for its resolved OpenCodex state directory before loading configuration or binding a listener.
References share one lease only inside one process and one directory; a different directory in
that process is refused while the lease is held, because the shared ledger is process-wide. The
refusal is about two directories owned at once, not forever: releasing the final reference
discards the singleton with its binding, so the same process may then own a different directory
and build a ledger by replaying that directory's own journal. A second process on the same
directory is refused even for observe-only spend configuration, while a separate directory is
independent. Ordinary stop drains the spend reporters after listener teardown before releasing the final
reference. src/server/index/spend-ledger-lifecycle.ts also holds the lease through failed-start listener rollback. Enforced seeds remain live until reporters close and settlement plus send-ID forgetting are durable; scope totals survive forgetting. SQLite and the OS release a crashed owner; no PID, timestamp,
TTL or lock-file deletion participates in recovery. Startup passes the provider roster and top-level pool aliases with the spend policy; live state reconciliation refreshes both without opening an unused ledger. Under the request-start policy, existing requests retain their initial enforcement mode, numeric ceilings and L; changed limits apply to later requests. HTTP admission checks historical continuity even without a root; failed accounting reads release the active-turn lease. Rooted continuity denials record exactly one workflow refusal event before response formatting.
An explicit Codex integration OFF skips startup cache invalidation before the user-scoped catalog
serialization lock is resolved. Explicit sync and sync-cache retain their catalog-only override.
startServer composes up to four sockets in one synchronous startup transaction: the public data listener, optional unauthenticated data-loopback and hub-management listeners, and the optional hub-link listener, which opens only for a recorded link and persists its concrete 127.0.0.1:<listenerPort>. HTTP Responses/Messages apply the caller header contract before turn admission and dispatch. Linked-machine data uses the connection-bound relay contract; client-local credentials and routing policy remain unchanged.
The data-loopback socket serves a fixed data-plane allowlist: Responses and its compact sibling,
the native search relay, the standalone Images POSTs, keyed file/stream transcription, GET /v1/models, the realtime voice shapes,
and the Anthropic and OpenAI chat wires the host's own local clients speak — POST /v1/messages,
POST /v1/messages/count_tokens, and POST /v1/chat/completions. It never serves /api/*,
/healthz, /readyz, or GUI routes, so local management discovery has to use an authenticated
surface with a management credential.
The hub-management socket is enabled only by runtimeRole: "hub" plus
hub.managementIngress.enabled, always binds 127.0.0.1, and default-denies everything except
GUI, session bootstrap/exchange, and /api/*.
The hub-link socket is HTTP-only and default-denies all but the fixed data routes, catalog, hub-state,
usage, and GET /readyz; every Upgrade header, management, GUI, session, health, and unknown
/v1/* route is rejected before dispatch. Its opencodex-link.invalid policy admits only configured
key ids recorded by links.json, never the environment token. Context relays rebuild that policy for their post-body admission check, so key revocation stops an in-flight request before dispatch. ensureStarted() is single-flight,
final deletion closes the listener, and src/server/index/optional-listeners.ts runs supervisor teardown before closing this listener and the Claude intercept pair.
At the end of the startup transaction, startServer also starts the optional Claude intercept pair
through src/server/index/claude-intercept-lifecycle.ts (fire-and-forget start, ownsListener for
the ingress decision, stop joined into the listener shutdown) from src/claude/intercept/runtime.ts: a loopback HTTP CONNECT proxy (src/claude/intercept/connect-proxy.ts)
and a loopback TLS listener (src/claude/intercept/listener.ts) that presents a leaf for
api.anthropic.com signed by a per-install authority (src/claude/intercept/local-ca.ts, persisted
under <OPENCODEX_HOME>/claude-intercept/ with a 0600 key; never installed into an OS trust store). CA reads and pair publication share a directory-bound SQLite lease; persisted certificates must match their private key and verify as a self-signed CA. Directory, certificate, key and lock paths reject symlinks, nonregular files and foreign ownership; pinned descriptors and exclusive unpredictable temporary files protect reads and publication. Windows verifies ownership and permits only the current user, SYSTEM and built-in Administrators in the private-file ACL, including before writing temporary key bytes; inspection or protection failure rejects the operation. Startup retries only lease contention with bounded asynchronous backoff before binding either listener.
Claude Code reaches the pair through an authenticated HTTPS_PROXY URL plus NODE_EXTRA_CA_CERTS in its settings env
(src/claude/intercept/settings.ts), so no ANTHROPIC_BASE_URL rewrite is involved and the client
still believes it talks to Anthropic. The proxy splices CONNECT api.anthropic.com:443 onto the TLS
listener, relays every other CONNECT target blind, and refuses unauthenticated clients, plain proxied HTTP, and loopback targets. The per-install proxy token is stored owner-only under <OPENCODEX_HOME>/claude-intercept/ (0600 plus a real per-user NTFS ACL on Windows, via src/lib/windows-secret-acl.ts, and re-pinned on every read-through ensure), and the settings file carrying it is written through the same hardened atomic writer. Every start runs migrateClaudeInterceptSettings (src/claude/intercept/settings.ts), which rewrites an owned env that no longer matches — e.g. a pre-auth URL left by an upgrade — while never creating an absent env or touching a foreign one, so a service restart cannot strand clients on 407s. Status/inspection reads the token without minting it; only apply and intercept startup create it.
The TLS listener rewrites POST /v1/messages and POST /v1/messages/count_tokens to a loopback origin and dispatches them under the claude-intercept ingress; other paths relay to the configured upstream. One relayed path is extended: for the standalone CLI's picker catalog, the listener's cliCatalog hook (src/claude/intercept/cli-catalog.ts) merges opencodex rows into GET /api/organizations/<org>/model_selector/cc (or /api/model_selector/cc), touching only the cc surface and leaving Desktop's ccd/code surfaces to picker mode, and into the fallback additional_model_options of GET /api/claude_cli/bootstrap. Eligibility: the cc catalog is rewritten only for a CLI-classified User-Agent with claudeCode.cliFirstParty on, because Claude Code caches it on disk for every process and a Desktop-spawned process must not seed rows a plain claude cannot route; the bootstrap request carries no entrypoint and follows CLI intent alone. Rows come from buildCliPickerModels (src/claude/intercept/picker-models.ts), which uses Desktop 3P registry aliases because the CLI offers only Claude-shaped ids (an ocx-claude-* row renders "Update Claude Code"), and advertises only an alias the registry decodes back to the same route. src/claude/intercept/cli-picker.ts supplies them lazily on the first eligible request, persists the snapshot at <OPENCODEX_HOME>/claude-intercept/cli-picker-models.json, re-checks each persisted row against the live registry (its alias must still decode to its recorded route; a cold registry after a restart is awaited first, and a snapshot whose rows still do not decode, for example after a provider change, is rebuilt within the same bound), and bounds a cold build at 2.5 s while awaiting the shared registry build in src/claude/desktop-3p-startup.ts that startup also uses (a failed or empty on-demand build is not retried for 30 s). Both CLI catalogs obey the shared picker rewrite bounds, preflighting row growth before copying and keeping the final JSON within 16 MiB. The rewrite fails open: a provider error, non-2xx status, unparsable body, or no added row relays the upstream bytes unchanged; a body over the 16 MiB cap is cut off while streaming and answered 502 rather than buffered. Claude Code trusts its cached <claudeConfigDir>/cache/model-catalog/*-cc.json for about an hour, so writeSettings in src/claude/intercept/settings.ts and every successful reconcileClaudeFirstPartySettings drop those files (leaving Desktop's *-ccd.json); running sessions keep their list until restarted.
The pair is on by default on a hub (claudeCode.intercept.enabled); its proxy port defaults to public port + 100 (claudeCode.intercept.port). The lifecycle records bind failure, and stop joins both sockets. Its serialized ensure() joins pending startup, reuses the live config and dispatch wrapper, and can start a previously unbound pair. stop() refuses future ensure calls and awaits an in-flight start. Local management applies and ocx claude intercept start (POST /api/claude-intercept/start) use this seam through src/server/index/optional-listeners.ts; hub-management ingress and data-plane credentials cannot start interception. Status reports interceptReason (with interceptFailurePort for a CONNECT bind failure) and optional pickerReason (with pickerFailurePort when the Desktop picker relay port is occupied), distinguishing disabled routing, client role, ephemeral public ports, occupied CONNECT ports and other failures. A changed configured port never rebinds a running pair: apply reports port_mismatch with both ports. With an ephemeral public port (startServer(0)), an explicit intercept port is required. The lifecycle makes a best-effort legacy picker-key cleanup attempt before checking client role, disabled routing/interception, or an ephemeral public port. Each new unbound lifecycle ensure() attempt retries cleanup; management requests rejected before that seam do not invoke it. Cleanup failures do not change the eligibility result or block startup. Direct startClaudeIntercept callers retain the same cleanup defense. Mock-only regression coverage lives in tests/claude-integration/claude-intercept-cleanup.test.ts.
This ingress honours first-party model bindings (claudeCode.intercept.modelMap); see Claude Desktop. Picker mode and the primitive's optional allowedTargets restriction are described under Desktop egress proxy. The macOS picker reuses an exportable signing identity protected by the OS credential store across normal restarts, without a plaintext key in the config directory. Restore verifies the constrained CA and current OS trust without installing trust; explicit picker on or trust grants missing or revoked trust. Legacy migration cleanup may still require consent, and native keychain unlock or application-access prompts are separate. Windows and Linux start no picker CA, credential-store or proxy work; the main intercept pair and best-effort legacy-key cleanup remain available.
src/claude/intercept/client-class.ts classifies each request by its Claude Code User-Agent entrypoint: claude-desktop, claude-desktop-3p, and local-agent are Desktop; other well-formed claude-cli/<v> (external, <entrypoint>) values are CLI; absent or malformed values are unknown.
Only a client with its own first-party intent enabled (Desktop mode or claudeCode.cliFirstParty) enters the router for Messages paths; other paths use the configured upstream relay.
Every path from an opted-out or unknown client, and every path while Claude routing is disabled, relays to real Anthropic through relayToUpstream with CLAUDE_INTERCEPT_UPSTREAM.
The User-Agent split is a routing hint any local process can forge, not a trust boundary.
Two independent intents can want that settings env: Desktop first-party mode and claudeCode.cliFirstParty for the standalone CLI. src/claude/first-party-settings.ts owns the union: reconcileClaudeFirstPartySettings writes the owned pair while either intent is on, keeps the file untouched while an intent is on but the intercept cannot run, and removes the owned pair only when neither intent remains. firstPartyProxyStatus classifies what the settings file currently points at against the bound listener (none, live, stopped, disabled, broken, foreign, local, unknown); it reads the proxy token and never mints it. ocx claude with Claude routing off launches natively; when the shared settings env carries opencodex's proxy it adds NO_PROXY=* and no_proxy=* so this launch bypasses it, unless an inherited foreign HTTPS_PROXY or https_proxy is present, in which case it warns instead. Successful CLI first-party saves and Desktop first-party apply with a persisted mode attempt best-effort roster reconciliation through the existing management helper after settings/picker reconciliation; refusal, rollback and unsaved Desktop partial success do not. Disabling first-party alone preserves registration while the separate integration/injection gates remain enabled.
Auxiliary listener bind failures carry the listener key and effective address through AuxiliaryListenerBindError in src/server/ports.ts. src/cli/index.ts reports them without retrying the public port. Startup still rolls back every earlier socket synchronously.
A failed public, loopback, or management bind rolls back earlier sockets; a failed hub-link bind warns
and exposes failed{bind} through optional-listener status while existing sockets remain available.
Listener-port persistence failures expose failed{persist} and close the new socket. Normal stop joins
sockets before release. The existing launchd/systemd installer remains the service owner and loads the data token from service-api-token; hub mode adds no service-manager fork or token-bearing unit/plist field.
Decision record: ADR-0002
The process-state boundary deliberately exposes two PID checks. readAlivePid() is the cheap
non-destructive probe used by liveness polling. readPid() and verifyPidIdentity() include the
fixed-path command-line check required before stop, kill, port reclaim, or stale-state deletion.
Callers must not replace the latter with the former merely to avoid the Windows WMIC/PowerShell
probe. Expected-PID and snapshot removal helpers are the TOCTOU boundary when a replacement proxy
can write new state during a probe.
Ownership of a pending-teardown receipt is decided by that same identity rule. The receipt records
the PID that accepted the obligation, and handleStop treats an owner as still running only when
the live PID is verifiably an opencodex process (isProcessAlive composed with
isLikelyOcxProcess). Bare liveness is not sufficient and is a regression here: the OS reuses PID
numbers, so once the owner exits an unrelated process can inherit its number, and a cheap probe
then reports the stop as still in flight for as long as that process lives. The receipt is filtered
out of the recovery loop and is never recovered, quarantined, or even mentioned, while both updater
gates keep seeing an outstanding obligation — a permanent fail-closed teardown-outstanding abort
with no proxy running and a dead owner (#4897). isLikelyOcxProcess asks the broader question than
verifyPidIdentity, without the start verb, because a receipt owner is an ocx stop or the
ocx update worker that drove it rather than the proxy. Recognizing a receipt as abandoned only
admits it to recovery; a valid receipt must still prove its recorded endpoint is down before
anything is restored, and the package launcher still decides nothing itself.
Port reclamation must honor a rejected OCX verifier result even for a PID captured before stop or update. A rejected live holder prevents both termination and TCP-row deletion for that scan; later scans may proceed if verification succeeds or the holder exits. The allowlist narrows termination eligibility and supplies no identity evidence by itself. This contract uses the existing verifier; it does not add process-instance proof or change the classification cache.
Pinned post-update retries in src/update/job.ts retire a child after an observed exit, signal, spawn error, or close event.
Both retry and final-timeout cleanup check the retained child object; a late exit from an older
child cannot clear its replacement. Retirement removes only its own event listeners and preserves
separate logging. A healthy child remains running. This does not provide an
atomic OS guarantee against unobserved PID reuse.
Decision record: ADR-0003
An installed Codex shim is checked on eligible CLI startup with a direct-regular-file/1 MiB state bound and bounded metadata/prefix reads. Changing launchers defer; failures warn without changing the requested command’s exit behavior.
All mutations hold the shared atomic-mkdir interprocess lock and reread state before final revalidation, publication and state commit. Unique token-named owner records prevent release from naming a successor.
Aged locks are reclaimed only when the owner PID is dead and the same token, directory identity and owner fingerprint remain immediately before deletion; age alone never evicts a live holder.
Windows repair retains complete sibling-set preflight, identical fingerprints/prefixes across a 100 ms observation interval, and reverse-order rollback if a later sibling changes. It restores stable external replacements without PATH rediscovery.
Unix installation owns only the private home/bin/codex wrapper and codex-shell-env.sh. The native manager-owned launcher stays in place; its recorded durable entry follows package upgrades and version rollback.
src/codex/shim-state-file.ts decodes schema 2, mode path-overlay, supported Unix platform, exact private wrapperPath, and distinct absolute launcherPath; contradictory compatibility fields are invalid. State can record wrapper/env inode identities and a transaction id.
Its compatibility view exposes one read-only backing tuple with original/backup/real paths all equal to launcherPath; mutation dispatches by mode/platform before interpreting that tuple as legacy backup data. State writes use exclusive same-directory 0600 staging and atomic publication.
Private publication stages and probes the wrapper, environment file and state under the lock, revalidates the native launcher, and journals prior and created file identities. The state commit marks completion; failures restore owned prior private files without overwriting concurrent replacements.
Explicit install alone migrates an unambiguous recorded legacy Unix shim: native restoration never replaces a newer entry, and recovery remains retryable. Failure after restoration never reinstalls a wrapper at the package-manager path. Missing/unusable native entries require manager repair.
Unix automatic repair refreshes only the recorded private wrapper, never native entries or a legacy in-place shim, and never discovers another installation. Current runnable content avoids loading config; otherwise repair lazily respects codexShimAutoRestore and its process opt-out.
Uninstall rereads state under the lock and removes only confirmed private artifacts, preserving the native launcher and concurrent replacements; older Unix shims use explicit recorded restoration. Users remove their own shell source line.
Activation is explicit and idempotent: sourcing codex-shell-env.sh puts the private bin first after manager PATH setup, without editing shell startup files or changing the installer’s parent shell. The file contains no service token.
Unix install-probe cleanup refusals retain their fail-closed behavior and bounded suffix: fixed phase, allowlisted native error/signal, and bounded exit status. Launcher paths, metadata and raw child errors never enter that suffix.
The probe uses a child-specific PATH and startup bypass; read-only config diagnostics are used only for confirmed candidates, never adjacent auth state. Its supervisor re-enters the executable with BUN_BE_BUN=1 even from compiled ocx; the saved launcher receives no interpreter flag.
Overlay diagnosis keeps runnable (owned executable wrapper/current binding and usable native launcher) separate from active (the selected PATH command is this wrapper); healthy requires both. Failed PATH inspection is unverified, not active.
Explicit codex-shim install (src/cli/dispatch.ts) refuses with nonzero exit; a runnable overlay succeeds even while inactive, prints activation guidance, and receives no green readiness claim until active and free of routing/proxy warnings. Healthy existing Windows shims still succeed.
Codex CLI update inspection is split from mutation. system codex-cli-update check makes no
package-registry request and reads bounded provenance evidence for the configured launcher candidate, npm ownership layout,
package metadata, and shim binding. The proof-bound launcher snapshot does not attest successful Codex execution;
environment and persisted candidates remain report-only and cannot produce a managed classification in this one-shot command.
On Windows this first slice performs no candidate/configuration filesystem I/O: it preserves only proof-captured
absolute environment candidates for lexical app-bundle/version-manager reporting and otherwise fails closed. That
fail-closed result records which observation was missing: a run with no proof-captured environment candidate reports
windows_inspection_deferred, because persisted selection is never consulted there and the command cannot claim that
no Codex CLI exists; a captured candidate whose path is not lexically eligible reports candidate_path_unavailable.
POSIX keeps candidate_unavailable for an unobserved candidate.
This check does not attest or admit a selected runtime. The command exposes no private mutation authority and does not query
a registry, execute Codex/npm, install, repair, stop, restart, or change configuration/cache state.
The bridge enforces a heartbeat stall deadline. It defaults to 300 seconds sampled on a 2 s tick
(src/stall-timeout.ts) and is configurable, so treat the number as a default rather than an
invariant; sidecars keep their own clocks. On expiry the stream is closed and the upstream request
cancelled. If the adapter generator ends without an explicit done/error event, the response is marked
incomplete rather than completed so Codex can distinguish a clean finish from a truncated stream.
On error / incomplete / stall / EOF — and when assembled non-freeform tool arguments fail to parse —
an open tool call is cancelled as status: "incomplete" without function_call_arguments.done, so
the client never sees a completed call ahead of response.failed / response.incomplete.
At the freeform boundary, src/responses/apply-patch-envelope.ts unwraps the contractual input
field for every tool. Only bare or default.-prefixed exec and apply_patch calls may recover
one recognized alternate body field or remove one complete outer Markdown fence; ambiguous
alternate fields and every other freeform grammar pass through unchanged.
The server exposes POST /api/stop which restores native Codex config (a sibling instance answers sharedTeardown: "not-owned" instead), stops any installed service
(to prevent respawn), and exits the process. The GUI sidebar stop button calls this endpoint.
Decision record: ADR-0004
src/cli/connect.ts resolves only through the first valid local Codex runtime for catalog
readiness, then reads that runtime's effort ladder without persisting its selection. Rejected
preferred candidates still fall back in priority order. General ocx status retains full runtime
discovery and passes its resolved command into readiness, avoiding a second version probe without adding cache state. ocx connect also prints a secret-free Codex shim readiness line from src/cli/codex-shim-readiness.ts: ready when the healthy shim is active on PATH, unhealthy when wrapper/native validation fails, missing/not active with the source command for a runnable inactive overlay, or unverified when the PATH scan throws. ocx status, ocx codex-shim status, and ocx doctor skip shim auto-repair; doctor retains unset-token warnings and recommends activation for inactive overlays rather than reinstalling them.
ocx config [show], including flags-only display, stays outside that lifecycle path. Argument selection follows the config reader contract. src/cli/config-command.ts reads the validated
config snapshot and the bounded service-token observation needed for its _remoteHub annotation;
it does not import the connect command, inspect catalog readiness, acquire lifecycle locks, or run
config/secret ACL hardening.
src/remote/protocol.ts owns pure interval/feature negotiation. src/remote/hub-state.ts owns the GET|HEAD /v1/hub-state contract, its caps, and the parser both sides share. src/client/hub-client.ts owns bounded, schema-validated remote catalog consumption, hub-state reads, and key-id probes; src/client/hub-state.ts owns the resolution and the owner-stamped 0600 cache, and a failed read reports "unavailable" rather than degrading to the client's own local provider and login state. src/client/hub-relay.ts is a fixed-authority management relay with URL, header, body, redirect, and stream bounds. The public data listener remains the direct client→hub path; the loopback management ingress never serves data-plane routes. A client with transport: "link" reaches its hub through an SSH tunnel instead; see Remote Link. A client with a link sidecar owns its SSH tunnel; see Remote Link.
src/cli/status.ts rereads the persisted client connection and service-api-token state before
requesting hub state. It passes a usable token only when the current connection matches the
status snapshot's serverUrl, apiKeyId, and connectedAt, and the token fingerprint matches
the current connection's tokenFingerprint. Otherwise it skips the live request and uses the
snapshot owner's matching cached hub state, or reports unavailable.
A withheld token carries its own cause into the reported reason through
resolveHubState's withheldTokenReason, so a changed connection, a missing token file, and a
fingerprint mismatch are named separately rather than all reported as a missing data key.
Codex display-cache expiry, retained blocking main-policy evidence, and reset history follow the quota cache contract; automatic exhaustion and recovery also use spendable credit evidence.
Usage consumers preserve positive incomplete-history metadata as specified in
usage accounting; readable totals are not represented
as a complete ledger. The same contract owns src/usage/log.ts append-path permission rechecks and
their bounded cache.
Connected ocx usage reads /v1/usage through src/client/hub-client.ts, using its enrolled data key and checking connection/token ownership before and after the read. It reports hub/client scope and never substitutes local totals on failure. Standalone commands retain their management endpoint.
The client usage read requires HTTPS or loopback HTTP before adding the enrolled credential, and sets request cache: "no-store"; the hub response also forbids caching.
The shared atomic replacement publisher identifies explicit Remote Workspace file writes as remote-workspace. Remote Workspace's separate, explicitly enabled server surface uses structural WebSocket callbacks and awaited per-server cleanup; its contract owns that integration and documents its isolated owner and support limits.
Chat helper admission in src/server/responses/request-sidecar-auth.ts follows the
deferred stored-main contract: only a needed Direct OpenAI helper
claims stored main, after terminal vision, routed vision and search exclusions.
Automatic Codex pool selection and account status share the plan exclusion contract.
Both search loops keep web_search declared after the search budget is exhausted. Further calls receive a paired limit-reached result without another physical search; at most maxSearches + 3 model iterations run before a terminal error. Ordinary caller tools and cancellation still end the loop. Empty-answer recovery alone removes all tools. src/web-search/loop.ts makes at most one extra answer attempt after a clean forced-answer terminal with no visible output or tool call, only while an iteration remains. The recovery has no tools and reuses gathered search results. Malformed calls fail before refusal/truncation passthrough, and well-formed recognized refusal/truncation terminals pass through unchanged, including empty or partial answers. The extra generation may incur provider usage. src/web-search/run-turn-loop.ts shares this recovery and checks the ceiling before dispatch so it cannot start an unused request; below the search cap, emptyCompletionRetry permits one identical empty-answer retry per request with current tools and results. Errors and invalid terminals never authorize another search. Batched results from one call share one 8,000-character budget in src/web-search/format-result.ts, planned per query before serialization (#6621): every query keeps its label and status, shortened answers and unlisted sources carry markers with their original counts outside the untrusted boundary, structured output stays one valid JSON document, and a batch whose fixed parts alone overflow is condensed to status lines with a per-status count of any query that still did not fit.
src/web-search/loop.ts uses src/web-search/header-deadline.ts to suspend the cumulative response-header deadline while each physical send awaits local pacing admission. Ordinary fetches, adapter executors, reset retries and pool-key rotations retain the remaining budget after admission; same-target 429 backoff still clears and restarts that budget. The default remains 200,000 ms. Client cancellation remains linked during queued admission and after final headers, and the existing pacing slot wrapper retains leases through body completion or cancellation. Streaming retry heartbeats and body inactivity deadlines keep their existing ownership. tests/web-search/web-search-pacing-deadline.test.ts covers queued success, real header stalls, queued cancellation, retry admission and cumulative rotation time. OpenAI sidecar 429 replays run only when their backoff fits the remaining sidecar deadline; otherwise the original 429 remains the routing-health outcome rather than becoming a timeout. Reset recovery and 429 replays share one three-send budget per search, so the two layers cannot multiply physical sends. Direct vision/search helpers and the alpha/search relay retain the resolver's live stored-account credit policy through physical sends and retries; a local hold prevents the send without becoming an upstream connection failure.
src/providers/quota/report-cache.ts publishes routing evidence only when a producer explicitly supplies its
inference-wide projection. A matching credential alone does not grant veto authority. Display-only
account, model-group, search and legacy MCP windows remain visible but cannot exclude a provider.
The private WeakMap binds provider name, adapter, destination and captured credential; neither
credential nor binding enters report JSON.
src/providers/quota-routing-cache.ts rechecks the live single key, effective authentication,
static credential headers and key-pool size. Unknown, invalid, future or 30-minute-old evidence
cannot rank or veto a provider. src/combos/resolve.ts uses that same scoped getter for selection,
reset-window ordering and catalog inactivity. Changing a key, destination or adapter invalidates
the old binding; restoring the same configuration may reuse still-fresh evidence. Account admission,
cooldowns and response-driven retry remain authoritative.
The management quota DTO keeps Combo editing aligned with scoped inference evidence; see Combo editor routing quota.
Canonical Spark Lite metadata follows the final serialized model and surviving nonempty Lite tool catalog; see Responses transport.
Optional Codex transport-hint suppression is scoped to canonical Responses client output; its defaults and exclusions are owned by Responses transport.
Responses route normalization resolves provider summary defaults from the original wire preference on every final route. See reasoning presentation and CCA summary provenance.
src/server/index/serve-options.ts establishes the authorized upstream live sideband before accepting the client WebSocket upgrade, and src/server/index/live-sideband.ts implements the bounded upstream dial. openLiveSidebandUpstream bounds the handshake to ten seconds and retains at most 32 frames and 1 MiB of preamble within the frame limit. src/server/ws-bridge.ts defines the runtime handoff carrying captured frames or terminal state. Failed handshakes return 502/504 and client cancellation returns 499; exact upstream 404/410 status is unavailable from Bun's client WebSocket. Admission ownership lasts until upstream close/CLOSED, including failed upgrades and failed attachment. The ordinary Responses WebSocket exchange remains separate.
The relay is transparent in both directions, and that includes the close: a downstream client's close code and reason are carried to the upstream through clientCloseForUpstream, which only substitutes 1000 for a code no endpoint may send and truncates the reason to the 123-byte control-frame limit. This matters to the caller, because a Frameless v3 client reads upstream 1000 as the session completing and any other code as a transport loss to reconnect.
OCX_LIVE_FRAME_LOG records both frame metadata and sideband lifecycle stages (upstream-open, upstream-failed, relay-attached, relay-closed) in one JSONL, content-free in both shapes. The lifecycle half is what separates a join that never reached this proxy from one whose upstream handshake was refused and from a live relay that carried nothing; frame records alone leave all three as an empty file. tests/server/server-live-realtime-fixtures.test.ts drives each sideband stage against de-identified Frameless v3 fixtures in tests/fixtures/realtime-voice-sideband/ so a failure names the stage.
Paginated and migration-capable history follows the authoritative writer contract; this document adds no independent writer guarantee.
Codex pool settings and their consumers follow the reset-first ordering contract, including independent-quota fallback, preserved affinity, strategy-specific threshold summaries, and shared short-observation freshness for switch warnings.
Claude replay carries Go conversation affinity privately to final dispatch; preliminary route selection does not inject Go-only headers.
Private pool credential metadata follows the quota-history publication identity contract; credential-only and account DTO projections omit it.
Cline CLI joins the existing export/client integration registries. Explicit CLI sync and POST /api/sync refresh its owned pair; unattended catalog refresh excludes it. See Cline paired files. Explicit CLI sync (src/cli/dispatch.ts) and management sync also refresh already-owned OpenCode and Kilo catalogs after publication, preserving unowned, removed and hand-edited blocks under the ownership contract.
Its paired-file writer uses the config atomic-write primitive that replaces the named entry without
following a final symlink, so an exchange during a mutation cannot redirect the write.
claudeCode.stabilizePromptCache is a default-off operator setting for translated instruction stabilization.
Config JSON preserves the boolean; only literal true activates the role-changing transform. Claude skill-bundle marker parsing follows the bounded inbound contract.
Devin CLI credential path composition in src/oauth/devin/cli-import.ts follows the selected platform: Windows uses Win32 APPDATA paths, other platforms use POSIX XDG-data paths. The explicit absolute override remains verbatim; credential parsing and login behavior are unchanged. The src/providers/devin-provider-merge-migration.ts startup migration treats the legacy provider row and its OAuth slot as one account-bound unit: an occupied destination or a refused config projection leaves both unchanged, and both backups complete before either file changes. The adapter takes a tenant host only from the stored account that owns the exact key being transmitted, in the literal slot or, during a detached rekey window, the alias slot, so separately configured or forwarded credentials and non-owning accounts cannot lend another account's destination. Native Devin alpha search likewise resolves OAuth from the routed provider name that admission checked, rather than borrowing the canonical devin slot for a custom Devin-adapter row.
Native Chat applies qualifying effort ceilings independently of model pins; pin selection precedes the cap and only pins or cap rewrites enter wire mapping. The catalog effort contract records the V1/compaction exemptions and caller-preservation boundary. Pool quota producers and account commands follow the bounded raw-observation contract, separate from the latest display snapshot and capacity estimates.
The account history response can include a low-confidence effective capacity estimate; usage normalization retains local-answer provenance so local responses cannot supply samples.
Account quota surfaces use safe probe diagnostics separately from quota validity, credential health and routing authority.
Translated Chat request construction uses the inline-image budget; the shared normalizer counts retained bytes even when a wire-specific drop callback keeps the image attached, rejects inputs above the safe decoded-pixel ceiling, caps native decode work process-wide, and stops queued work when the request is cancelled.
OpenCode catalog discovery in src/cli/opencode.ts derives a catalog-only bearer from the local admin credential and uses a validated numeric-loopback management origin. src/server/management-auth.ts accepts that derived bearer only for the exact GET /api/models read, so a spoofed listener cannot capture reusable administrator authority; that read can still finalize a pending initial model selection, so the bearer is catalog-scoped rather than strictly read-only. The launcher dials through src/server/direct-local-http.ts, rejects redirects and preserves the request/body deadline. Hub ingress selection stays separate from exported inference settings.
The explicit model-capability contract preserves operator declarations through provider storage and catalog capture. Vision dispatch consumes those declarations together with registry/vendor metadata before any image-bearing upstream send.
The anthropic OAuth and anthropic-apikey presets in src/providers/registry/entries-core.ts
declare modelInputModalities: ["text", "image"] per model for the nine Claude seeds in
src/providers/registry/model-seeds.ts. Existing enrichment fills missing entries while preserving
explicit operator overrides; unknown models receive no new declaration. Client eligibility filters
and Anthropic image wire handling remain unchanged.
src/vision/plan.ts prevents raw image bytes from reaching any target whose effective capability is positively known to exclude image input. Evidence is consulted highest-first: modelCapabilities, an explicit custom row for the same routed identity, noVisionModels, an explicit per-model modality list without image, then backend-specific/registry/vendor metadata. A proven text-only target is preprocessed through the configured Vision Sidecar; a positively image-capable target receives the image directly. Genuinely unknown custom models retain the existing compatibility path rather than being guessed text-only.
Canonical ChatGPT Codex forwarding uses the generated openai-codex capability bundle rather than the public openai bundle. This matters when the two backends differ: for example, the vendored metadata records gpt-5.3-codex-spark as text-only on openai-codex while the public OpenAI row lists image input. The native Chat fast path and web-search image verbalization consume the same effective-capability decision.
An explicitly configured routed visionSidecar.model is dispatchable unless capability evidence positively proves it cannot accept images; an unknown custom sidecar is not guessed blind. If a proven text-only main target has no usable sidecar plan, image parts are stripped before the upstream request rather than forwarded raw. modelInputModalities is symmetric evidence: ["text","image"] proves image support while ["text"] triggers preprocessing. Runtime provider hooks such as injected fetch functions are preserved without mutation during capability enrichment.
Regression coverage: tests/vision/vision-cache.test.ts, tests/vision/vision-eligibility.test.ts, tests/vision/vision-routed.test.ts, and tests/adapters/openai/openai-chat-native-policy.test.ts.
Provider-scoped approval reviewer settings are projected by the catalog owner; this surface retains its existing routing, transport and account-selection behavior.
Renamed fixed-key providers receive missing reasoning metadata during derivation; explicit per-model entries and provider defaults retain precedence. The catalog sync owner bootstraps supported destination effort metadata with a bounded wait before gathering; routed reads of an existing stale ladder request a background refresh, while missing snapshots wait for catalog sync.
Translated audio/file admission follows the final-adapter input contract; native raw passthrough remains separate.
Google's final adapter compiler may emit an opt-in, content-free tool-schema loss diagnostic. It observes adapter-local narrowing only and changes neither provider routing nor the serialized request body.
src/adapters/openai-responses.ts omits only top-level user at the canonical ChatGPT Codex forward destination. Claude translation retains its original identity and prompt-cache key; public API and noncanonical gateways retain their user field. Input roles, tool-schema properties, safety identifiers and original replay bodies are not changed.
src/combos/failover.ts treats four intact HTTP 400 invalid-request envelopes as request-local incompatibilities: exactly Unsupported parameter: user; unsupported_value naming reasoning.effort or reasoning_effort with an explicit unsupported-value message; param: input with a bounded model-scoped does not support image inputs message; and the exact null-code gpt-6-astra function-tool routing mismatch that tells an existing Responses request to use /v1/responses, allowing only the bare model name or its strict YYYY-MM-DD deployment suffix. A null provider code is accepted only for the two observed envelopes that require it. Only the exact proxy wrapper is unwrapped, within three envelopes and 16,384 characters; conflicting codes, malformed/truncated envelopes and reflected JSON do not gain hop permission.
A response_format capability refusal is a fifth envelope, kept separate because it needs one code and one frame the four above do not admit. The refusal must name response_format AND state that it is unavailable or unsupported; a message that merely names the field, such as an invalid-schema complaint, stays terminal, because replaying a malformed request at every later target is the outcome this distinction exists to avoid. param may be absent or explicitly null and a param naming another field fails closed. Its code set is the shared generic one plus invalid_parameter_error, held separately so the user and image branches are not widened by it. It also unwraps a single data: SSE frame on a one-line body — the reported gateway answers on the stream, so the error object is never extracted and the structured code arrives undefined — while a multi-event body is left alone. The next target receives the same request with response_format intact: no field is dropped and the output contract the caller asked for is unchanged. Traversal stays finite because combo excludes each attempted target. This verdict records no cooldown, and cancellation, origin/cyber-policy rejection and the non-replayable post-send codes are all tested before it (#4903).
The combo may advance to its next eligible unattempted target before output commitment. It records no target/provider cooldown for these request-local mismatches and does not silently drop reasoning controls or raise none to a supported rung. Cancellation, origin/cyber-policy rejection, non-replayable post-send errors and the existing streaming commit boundary stay authoritative. Apart from the definite context overflow below, other invalid requests remain terminal.
A definite context-window overflow is the fourth request-local verdict. A heterogeneous combo mixes windows, so "this turn does not fit THIS model" is not "this turn is impossible", and stopping at the first undersized target burned the ladder on turns a later target could hold. Evidence must come from the innermost provider message: classifyError remaps any occurrence of context window, context length, maximum context or too many tokens anywhere in the blob, and inheriting that looseness would let a context_length_exceeded token sitting in a code field beside Unsupported parameter: user authorize a replay. src/combos/failover.ts therefore unwraps only the exact proxy wrapper, within four envelopes and 16,384 characters, and reads the leaf message. A JSON-shaped body that does not parse fails closed, because normalizeUpstreamErrorText caps classificationText at 500 characters and a long envelope arrives here as a prefix. The verdict is admitted only for statuses that speak about the request — 400, 413, 422 and 5xx — so a 401/403 body that merely quotes context prose keeps its provider-wide cooldown instead of being rescored as request-shaped. Structured origin_rejected, cyber policy and the non-replayable post-send codes are all tested before it.
This is also why the classifier cannot duplicate visible output. Native byte streams reach combo classification only through preflightComboStreamResponse, which commits the child on any text, tool call or unknown event and synthesizes a failure envelope only for a zero-output terminal. A runTurn adapter has the equivalent boundary in preflightAdapterEvents: when the first meaningful event is an undeclared tool call and no replay-unsafe heartbeat recorded a side effect, src/server/responses/run-turn-execution.ts checks it against the exact current request catalog and projects the existing fail-closed refusal as a pre-commit 502 so failover can continue without changing the catalog. Any earlier text, tool call, control boundary, unknown event or replay-unsafe heartbeat commits that child, so a turn whose output the client may have seen or whose side effect may have run is never replayed.
Regression coverage: tests/responses/responses-forward-prompt-envelope.test.ts, tests/routing/router-combo-failover-classification.test.ts, tests/routing/routing-policy-fallback.test.ts, tests/helpers/combo-context-overflow-cases.ts, and tests/server/server-combo-failover-e2e.test.ts.
src/combos/failover.ts uses a 10-minute fallback for a spent account usage window (codes usage_limit_exceeded, usage_limit_reached, 1308, or usage limit reached / usage limit has been reached / token-plan <window> quota has been exhausted prose, including HTTP 502) and for provider-scoped credential or billing failure codes such as invalid_api_key and insufficient_quota. This duration does not change failure classification or cooldown scope; upstream retry/reset signals and configured durations retain precedence. It caps explicit upstream Retry-After target cooldowns at 24 hours while reset-derived, configured, and fallback cooldowns remain capped at 10 minutes. src/combos/resolve.ts omits a target cooldown for a 429 only when the failed child identifies one account in an Anthropic OAuth or canonical Codex pool and that account has a live pool cooldown. src/server/responses/core-combo.ts checks the pool's health record before passing that attribution; unknown accounts, ambiguous labels, API-key and generic OAuth providers, and other failures keep their ordinary target cooldown. The Anthropic OAuth pool's own all-accounts-cooled 429 is the one unattributed exception: isAnthropicPoolLocalRefusal in core-combo.ts requires the anthropic provider with authMode: "oauth", status 429, no identified failed account, no eligible pool account and a live pool retry-after. The target then still cools, but for the local fallback instead of the restated per-account Retry-After, so a resumed or newly added account is not ignored until a 24-hour target cooldown expires. An upstream 429 on an API-key provider or on an identified pool account keeps its full Retry-After. Regression coverage: tests/routing/always-on-429-failover.test.ts. Cooldown entries in src/combos/failover.ts retain the failure status. The quota-only snapshot query records target/expiry pairs for unexpired 429/402 cooldowns using only cooldown map lookups. Its config-aware wrapper in src/combos/resolve.ts applies the picker's provider availability and cached quota checks before dispatch; request eligibility runs lazily only when exhaustion considers a 503, with throwing checks treated as ineligible. Responses exhaustion uses this evidence under the failover contract; ordinary cooldown selection and timing stay unchanged.
src/combos/request.ts keeps reasoningEffortMode and defaultEffortMode independent.
The existing fifth argument remains the strict/adaptive capability-normalization policy;
the optional sixth argument enables fallback/force precedence. Force requires a valid
non-null default, overrides only valid caller effort on a known supported ladder, and
retains the existing unsupported-control stripping. It does not add a caller opt-in or
change target selection. src/server/responses/core-combo.ts applies the policy per child
and preserves the original requested effort separately from effective wire telemetry.
src/server/chat-completions.ts routes combos through that same child pipeline while
retaining the current config-aware native-Chat eligibility check for non-combo routes.
Shared response-log retention and native SSE inspection pacing follow the bounded inspection contract; other subsystem behavior remains unchanged.
src/codex/account-label.ts owns the provider/selection digest and src/providers/label.ts
stamps the configured key selected for the physical request. src/server/request-log.ts
retains per-key attempt usage, and src/usage/log.ts validates and persists labels. The
account attribution contract
defines identity, unknown records, and aggregation boundaries.
Native steering retains fixed phase deadlines and reconciled replay output; see the steering stability contract.
Native steering generation overrides, explicit public-API eligibility and the consent-gated wire probe follow the shared control contract; this owner does not change routing or execute diagnostic tools.
Unicode pattern normalization uses copy-on-write traversal while preserving the existing schema and wire semantics.
Codex compaction uses a request-local model override for the configured triggers; the Responses compaction contract owns its trigger and replay boundaries.
src/service/state.ts records who owns the running proxy in the shared service install
state, beside the install provenance. The claim carries an owner (cli or desktop), an
opaque installId naming the owning installation rather than the user or the machine, and a
consentGeneration. An absent claim means the CLI install that registered the service owns
the runtime, which is what every record written before the field existed says. src/service/desktop-startup.ts separates the durable macOS and Linux desktop claim from startup viability. The bounded probe verifies matching install identity, loaded and enabled login registration, and exact app-to-bundled-proxy process paths. On Linux, auto-launch 0.5.0 writes the login entry under HOME/.config/autostart, so a relocated XDG_CONFIG_HOME (outside that search path) is uncredited. Conditional entries and ambiguous Exec syntax are uncredited; the resolved desktop basename must match and its resolved ocx stays in the same directory. Both complete evidence reads must agree on ownership, install-id, entry contents, executable targets, PID and parent relationship; changes fail closed. AppImage registrations are uncredited because they name the outer image rather than the mounted executable. src/server/startup-health-cache.ts launches source and compiled probes with selfLaunchArgv, revokes stale protection, and preserves desktop recovery guidance.
Every write goes through swapServiceInstallState. With a custom home, the default-home
record is the authority every writer can derive and the active-home record is a compatibility
mirror; with one path, that path is authoritative. src/service/state-lock.ts holds
token/PID/process-instance locks for every path in canonical order. A live holder is never
evicted because of age, and release deletes only its token-named owner. The authoritative
file is fsynced and atomically renamed through src/config/atomic-write.ts; that rename is
the commit point. Mirrors receive the exact committed bytes afterwards. A mirror failure is
diagnostic rather than rollback, and the next writer repairs it. An absent authority imports
one valid legacy mirror once; same-or-newer mirror disagreement and unreadable authority are
unknown, never ownership votes. Uninstall removes mirrors before the authority, so a
partial deletion cannot turn a revoked mirror claim back into migration input.
resolveServiceOwnership answers none, owned or unknown from that authoritative
generation. consentGenerationCeiling survives a release, so granting, releasing and
granting again cannot reuse a number an app-local record may still hold.
recordServiceOwner requires the exact owner/installId/consentGeneration/revision
subject shown on the consent surface. The comparison runs again inside the same lock and on
every internal retry; a mismatch or unknown subject writes nothing and requires fresh user
approval. ownershipGrantedTo remains the narrower relaunch test for an already-owned app.
Permanent takeover also requires assessServiceTakeoverCompatibility to approve both the
preserved service launcher and the selected PATH launcher. Every observed manager must be
OpenCodex 2.61.0 or later, and a preserved registration must carry ownership protocol 1.
Missing, old, malformed or unknown manager evidence blocks takeover and leaves registration
and autostart untouched. The supported verdict carries an opaque token over the approved
subject and both manager identities; recordServiceOwner re-observes and compares it inside
the lock, so a mutable shim or downgrade cannot inherit earlier consent. An upgrade is a
separate user-authorized action; declining or failing it leaves the app a guest. ocx service claim in src/service/claim.ts accepts the expected subject and compatibility token and calls recordServiceOwner under the ownership mutation lease. The CLI rechecks both before writing; a mismatch writes nothing. The desktop owns the consent prompt and treats approval-changed, manager-still-active, unreadable stop output and stop-child timeout as terminal before its silence wait or claim. Only a parsed stopped result or a validated exit-79 history-incomplete result reaches that wait. A stopped but unclaimed runtime is reported as such, without a restoration claim.
The verbs that activate the npm registration refuse on a foreign or unknown owner:
src/service/repair.ts stops before it asserts, writes, stops or starts anything, and
ocx service start reports the same refusal. stop and uninstall are not gated, because
they deactivate. src/update/runtime-ownership.mjs vetoes both the pre-update stop and the
post-update service refresh for all three update lanes — src/update/index.ts,
bin/ocx.mjs and the dashboard worker in src/update/job.ts. The shared update decision has
three independent authorities: package replacement, runtime stop and service restoration.
Unknown and desktop ownership deny all three because a claim alone does not prove that the live
process is detached from the npm package; CLI ownership permits the ordinary stop-first
flow. Both package updaters use src/service/install-state-contract.mjs, backed by the single
state-record.mjs parser and authority selector. One mutation lease covers the fresh stop
authorization, the stop child, the current runtime-record re-read and package replacement.
The updater never treats the pre-stop address as proof that this installation is idle;
an unreadable current record is unknown, and a valid address is probed even when its recorded
PID is gone. Lease delegation is passed only to stop and recovery children, never package
manager children. A replacement refusal passes through owner-aware recovery: only the same CLI
owner revives the stopped runtime; foreign ownership stays transferred and unknown ownership
remains a reported recovery requirement. Dashboard restart delegates the lease token to its service repair child, and releases the lease before that refresh: the service manager spawns ocx start outside the worker's process tree with no token, so the managed child must take the lease itself (#5760). Before the direct-start fallthrough kills or spawns on the port, the worker takes the lease back — a refresh that stayed claimed stops the restart — and re-runs the recorded-owner veto under it because a claim could have landed during the unleased refresh window. Direct start holds the same lease through bind plus PID and runtime-address publication. If listener rollback cannot prove the socket closed, the process retains its lease until exit.
The registration is never deleted; ocx service install releases the marker only after the
registration succeeds. Busy lease diagnostics name the recorded PID, current liveness, optional executable (omitted by default on Windows), and lease age. The process identity is unverified because a PID may be reused.
Bun updater lease and recovery behavior follows the update transaction contract.
Companion timeline and filtered totals follow the companion usage contract. Ongoing priority failback reuses request-triggered quota priming and captured-account dispatch; it adds no periodic worker or mid-request account switch. The serving-install census and bounded foreground delegation in src/config/serving-runtimes.ts follow service command selection. The live main-account policy projection is fetched from the attested account-list management API. src/cli/status-oauth.ts renders its state and effective thresholds with an optional outside-usage advisory; src/cli/status.ts includes the same whitelisted projection in ocx status --json. No policy state is inferred from the CLI process's local quota cache when the live read is unavailable. The experimental macOS ocx chatgpt launcher, restore and status commands follow the ChatGPT Desktop contract; the internal stdout filter remains hidden from public capability discovery.