Repository navigation
[AI-1762] guard-1: suppress the codex hook watcher for envelope-sourced sessions - #617
Conversation
…ed sessions A hosted app-server codex session will (once the activation slice flips it) push its transcript through the daemon's envelope path. To stop the rollout watcher double-ingesting the same session, the daemon stamps KCAP_HOSTED_APPSERVER=1 on the codex process and the codex hook stands its watcher down. Marker (daemon): BuildEnv sets KCAP_HOSTED_APPSERVER only when the runtime is emitting envelopes. The marker and the runtime's own emission now read ONE emitEnvelopeTranscript decision in StartAppServerAsync, so they can never disagree — the activation slice flips both by flipping that single local. It is false today, so the marker is never stamped and the shipped reviewers (still hook-ingested) keep their watcher. Suppression (hook): the SessionStart and Stop watcher-spawn sites skip EnsureWatcherRunning when the inherited marker is present. Only the watcher (the transcript-forward vector) is suppressed — the idempotent lifecycle POSTs and the cross-session spool drain are untouched, so the "working" indicator still clears. Tests: BuildEnv gating (marker present iff emitting) + a dormant-path proof that a real app-server launch stamps no marker today; hook suppression at both sites with a positive control (no marker -> watcher spawns). Linear-id-free per the guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoSuppress Codex watchers for envelope-sourced hosted sessions
AI Description
Diagram
High-Level Assessment
Files changed (4)
|
Code Review by Qodo
1.
|
…qodo/CI) qodo: BuildEnv only overlaid KCAP_HOSTED_APPSERVER, so a child could inherit a stale marker from the daemon's own environment and — while NOT emitting envelopes — suppress its hook watcher, losing transcript ingestion. Materialize the child env through ApplyChildEnv, which clears any inherited marker before applying the overlay, so the child's marker reflects only this launch's emit decision. Added two ApplyChildEnv tests (inherited-clear when dormant; set when emitting). CI: the three hook tests reused session_id "abc", which collides with the real-watcher test's registration — EnsureWatcherRunning fast-paths on IsWatcherAlive(key), so the positive control observed zero spawns under CI ordering. Give each test a unique session id. Also trimmed the two comments qodo flagged as verbose. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erver (#624) * [AI-1762] §2.6: activate envelope-sourced transcript for hosted app-server Flip the app-server path from the dormant hook-sourced transcript to the live envelope source. One envelopeSourced decision in StartAppServerAsync now drives all four coupled facts so they can never desync: the guard-1 marker (BuildEnv → KCAP_HOSTED_APPSERVER, stands the rollout watcher down), the runtime's envelope emission (emitEnvelopeTranscript), the deferred first turn behind the source claim (deferFirstTurn), and the forwarder wiring (Transcript: runtime on HostedRuntimeStart). Everything downstream was already built and merged and keys off these: the orchestrator drains the runtime's envelopes through AcpTranscriptForwarder and, seeing RequiresSourceClaimBeforeFirstTurn, runs source-claim → BeginFirstTurn → confirm (#612); guard-2 refuses any stray hook write server-side (#1547); death-close is the existing finalize → EndAgentSession, and the 60-min rollout-idle fallback disengages automatically because the stamped marker suppresses the hook's watcher (#617). So this is a four-value flip in one method, not new machinery. Scope: this activates the app-server path, which today is reviewers only (UsesAppServer stays `active && IsReviewFlow`) — the interactive routing flip is deliberately the LAST step, after §2.6 lifecycle + §2.7. Gated behind codex.transport=app-server (default pty), so a default daemon is byte-unchanged; requires a server carrying #1530/#1547 (server-before-CLI); and AI-2110's live cert validates it before app-server becomes the default. Only the dormant-marker factory test inverts (now asserts the marker is stamped, the Transcript is wired, and the first turn is deferred). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [AI-1762] §2.6: a cancelled source-claim must not dispatch the first turn (qodo) qodo reliability bug (latent in #612, armed by this activation): a launch that finalized during the source-claim / forwarder setup could still release the held first turn. StartForwarderAfterBind aborts silently when finalize cancelled acpCts and/or removed the agent, but StartEnvelopeSourcedSessionAsync then called BeginFirstTurnAsync unconditionally, and BeginFirstTurnAsync unsealed the dispatcher (sending turn/start) BEFORE observing its token — so turn/start could race the teardown. Two-part fix: BeginFirstTurnAsync now ThrowIfCancellationRequested BEFORE Unseal (the dispatch runs on CancellationToken.None and can't be recalled, so this is the only safe gate); and StartEnvelopeSourcedSessionAsync re-checks the SAME liveness StartForwarderAfterBind guards on (cancelled OR agent-removed) before dispatching, so the agent-removed-but-token-live arm is covered too. Both land in the OCE/return arms that already tear down or log-and-abort. Added Cancelled_BeginFirstTurn_does_not_ dispatch_the_held_turn. Also trimmed the activation test comment qodo flagged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
What
Guard-1 of §2.5's ingestion dedup (the daemon half; guard-2 is the server per-write source refusal). A hosted app-server codex session will — once the activation slice flips it on — push its transcript through the daemon's envelope path. This guard stops the rollout watcher double-ingesting that same session.
CodexHostedAgentRuntimeFactory.BuildEnvstampsKCAP_HOSTED_APPSERVER=1on the spawned codex process only when the runtime is emitting envelopes. The marker and the runtime's own emission now both read oneemitEnvelopeTranscriptdecision inStartAppServerAsync, so they can never disagree — the activation slice flips both by flipping that single local.CodexHookCommandskipsWatcherManager.EnsureWatcherRunningat the SessionStart and Stop spawn sites when the inherited marker is present. Only the watcher (the transcript-forward vector) is suppressed; the idempotent lifecycle POSTs and the cross-session spool drain are untouched, so the idle-marker stop POST still clears the "working" indicator.Dormant
emitEnvelopeTranscriptis hardcodedfalsehere, so the marker is never stamped and the shipped reviewers (still hook-ingested) keep their watcher exactly as today. The activation slice (PR6) flips the one decision, and guard-1 + guard-2 + the emission all come alive together.Tests
BuildEnvstamps the marker iff emitting; a real app-server launch stamps no marker while dormant.Comments are Linear-id-free per
scripts/check-linear-ids.sh(verified locally).Part of AI-1762.