Repository navigation
[AI-2333] Graceful Codex app-server shutdown: close stdin so park teardown doesn't SIGKILL - #690
Conversation
…n doesn't SIGKILL CodexAppServerHostedAgentRuntime.RequestGracefulStopAsync sent turn/interrupt and ONLY when a turn was in flight — so a between-rounds park (reviewer idle) made it a total no-op. The app-server process never exited, the daemon waited out the full 15s graceful window and SIGKILLed (exit 137), delaying AgentUnregistered ~16s on every park and widening the resume heal-block window (the kcap-cli half of the park-resume issue). codex app-server exits cleanly (code 0) on stdin EOF (verified against codex-cli 0.147.0). Add CodexAppServerConnection.CloseInputAsync (disposes only the write stream, leaving the read loop to observe the peer's own exit) and call it unconditionally at the end of RequestGracefulStopAsync, after the now-conditional turn/interrupt. Best-effort: if the peer ignored EOF the caller still falls through to WaitForExitAsync -> TerminateAsync, so this can only shorten teardown, never wedge it. Test: CloseInputAsync_ends_the_write_side_so_the_peer_reads_eof over the in-memory pipe harness (the peer reads EOF exactly as a real app-server's stdin would). Full Codex app-server harness suite green (44/44). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR Summary by QodoGracefully stop Codex app-server by closing stdin
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1.
|
…can't be abandoned Disposing a SemaphoreSlim does not release an outstanding async waiter: a WriteLineAsync or CloseInputAsync bounded WaitAsync already queued when DisposeAsync ran would then hang forever, neither completing nor honoring its timeout — so graceful stop could burn the full 15s window despite the peer already exiting (found by adversarial review, with a repro). Match the PiRpcProcess pattern: leave _writeGate undisposed in DisposeAsync, and turn away post-dispose writers with IsDisposed checks before AND after acquiring the gate. CloseInputAsync's bounded 1s wait now always honors its timeout. Add a regression test. Codex app-server harness suite green (45/45). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the prior-implementation narrative, park-incident details, exit code, and observed delay; keep only that closing stdin requests app-server shutdown and hard termination is the fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
What
The Codex app-server hosted reviewer never shut down gracefully on a park, so every park teardown 15s-timed-out to SIGKILL (exit 137) — delaying
AgentUnregistered~16s each time. This is the kcap-cli half of the §2.7 B6 park→resume issue (server-side tracked in kurrent-io/kcap-server#1715 / AI-2333).Root cause
CodexAppServerHostedAgentRuntime.RequestGracefulStopAsyncsentturn/interruptand only when a turn was in flight:At park time the reviewer is idle between rounds, so
CurrentTurnIdis null and the method returned immediately without touching the process. Even mid-turn,turn/interruptonly cancels the turn — it never asks thecodex app-serverprocess to exit. SoHasExitednever flipped,WaitForExitAsync(15s)always exhausted, and teardown fell through toTerminateAsync's SIGKILL on every park.How
codex app-serverexits cleanly (code 0) on stdin EOF — verified empirically againstcodex-cli 0.147.0(a barecodex app-server < /dev/nullexits 0 with no handshake).CodexAppServerConnection.CloseInputAsync()— disposes only the write stream (the peer's stdin), leaving the read loop to observe the peer's own exit via stdout EOF. Serializes with an in-flight write via the write gate (bounded), idempotent, best-effort.RequestGracefulStopAsyncnow: (1) interrupts the in-flight turn if one exists (unchanged, best-effort), then (2) unconditionally closes stdin so the process exits inside the graceful window. If the peer ever ignored EOF, the caller still falls through toWaitForExitAsync → TerminateAsync— so this can only shorten teardown, never wedge it.Verification
CloseInputAsync_ends_the_write_side_so_the_peer_reads_eofover the in-memory pipe harness — the peer reads EOF exactly as a real app-server's stdin would; read side stays usable; idempotent.CodexAppServerConnectionTests+CodexAppServerHostedAgentRuntimeTests).🤖 Generated with Claude Code