Repository navigation
Let one daemon host interactive Codex on app-server without moving a fleet - #736
Conversation
…fleet Two gaps stopped an operator opting a single daemon into app-server: The transport selection never reached a supervised unit. KCAP_CODEX_TRANSPORT is read by the daemon from its own environment and nowhere else — no profile or config-file binding — so `daemon service install` silently dropped it and the daemon ran PTY while the operator believed they had selected app-server. Captured now, alongside the reviewer consent switches that carry for the same reason. Interactive launches were hard-gated to PTY, so app-server could only ever host unattended reviewers. That gate is now a per-daemon opt-in (KCAP_CODEX_APPSERVER_INTERACTIVE): where it is off, nothing changes; where an operator turns it on, that daemon hosts interactive Codex over app-server while every other daemon — and every customer — stays on PTY. It widens which launches take the transport, it cannot select the transport, so a PTY daemon is unaffected. Scope worth knowing: this covers launches the SERVER dispatches (the web launch dialog, PR review, review flows). `kcap agent start` spawns an attachable terminal straight through the local control socket, never reaching the runtime factory, so it stays PTY regardless of the opt-in. Both tests are mutation-checked: dropping the keys from the capture list fails the service-install test, and restoring the hard review-flow gate fails the opt-in test.
PR Summary by QodoEnable per-daemon interactive Codex app-server routing
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
Review found the predicate said 'not a review flow', which also swept PR review onto app-server on an opted-in daemon — a launch class the operator never opted in and the switch does not name. It now states interactive positively: neither a review flow nor a PR review. Also drops a stale summary that still claimed interactive always takes PTY, and extracts the env spelling into CodexTransportDecision.IsInteractiveOptIn so the operator's only lever is pinned directly rather than exercised through a hand-set bool: affirmative spellings turn it on, everything else — including '0', 'false' and a typo — leaves it off. The PR-review case is mutation-checked: restoring the negative form fails it.
The variable names and the present-but-empty guard are the operator's contract — these are read from the environment and nowhere else — but nothing covered the wiring between the parsing helper and the config field, so a renamed key would have passed every test while leaving a daemon on defaults. BindFromEnvironment takes the lookup as a delegate and binds both codex transport keys; DaemonRunner passes Environment.GetEnvironmentVariable. Tests pin the exact names, that unset and present-but-empty leave the existing setting alone, that an unrecognised value keeps the opt-in off, and that the two keys are independent — selecting app-server does not by itself opt interactive in. Mutation-checked: renaming the interactive key fails the binding test.
…booleans Exact untrimmed matching read TrUe, ON and a shell-quoted " true " as OFF, so an operator could set the variable and keep launching interactive Codex on PTY with nothing to show why. Trim and compare case-insensitively, as ParseDebugFramesFlag already does, over a superset of its vocabulary. Mutation-checked: restoring exact matching fails five spelling cases.
Unblocks the AI-2110 certification strategy: opt one daemon into Codex app-server — for hosted agents and unattended reviewers — while everyone else keeps PTY.
Two gaps this closes
1. The transport selection never reached a supervised unit.
KCAP_CODEX_TRANSPORTis read by the daemon from its own environment and nowhere else — no profile or config-file binding — sokcap daemon service installsilently dropped it and the daemon ran PTY while the operator believed they had selected app-server, with nothing in the unit to show otherwise. It is now captured, alongside the reviewer consent switches that carry for exactly the same reason. (Found during the AI-2110 cert: the cert daemon had to run unsupervised because of this.)2. Interactive launches were hard-gated to PTY.
UsesAppServerrequiredctx.IsReviewFlow, so app-server could only ever host unattended reviewers, and AI-2110's interactive ACs (AC1–AC3) were unreachable. That gate becomes a per-daemon opt-in:Where the opt-in is off, nothing changes. Where an operator turns it on (
KCAP_CODEX_APPSERVER_INTERACTIVE=1), that daemon hosts interactive Codex over app-server while every other daemon — and every customer — stays on PTY. It widens which launches take the transport; it cannot select the transport, so a PTY daemon is unaffected by it.Why an opt-in rather than the flip
The alternative was dropping
&& ctx.IsReviewFlowoutright, which moves an entire fleet at once and can't be certified against production without exposing everyone. This makes the interactive path certifiable on a single daemon, against a real deployment, with a blast radius of one.Scope worth knowing
This covers launches the server dispatches — the web launch dialog, PR review, review flows.
kcap agent startspawns an attachable terminal straight through the local control socket and never reaches the runtime factory, so it stays PTY regardless of the opt-in. I verified that empirically: with the gate flipped locally,kcap agent start codexstill came up ascodex --cd … --no-alt-screen, the PTY launcher.Tests
Codex_transport_selection_survives_a_service_install(both platforms).UsesAppServer_admits_interactive_only_where_the_daemon_opted_in— 4 cases including opted in but transport still PTY → PTY, so the opt-in provably cannot select the transport.Both mutation-checked: dropping the keys from the capture list fails the first; restoring the hard review-flow gate fails the second.
🤖 Generated with Claude Code