fix: surface silent adapter launch failures as session errors - #1235
Merged
Conversation
When a wrapper adapter's harness child rejects a CLI flag or fatal config and exits before emitting a single session event, the failure only reached the daemon log via the stderr logger; the session flipped back to awaiting input with an empty transcript and looked hung. Add a bounded stderr tail (spawn_stderr_tail) and a shared emit_launch_failure_if_silent helper in adapter-common that emits a SessionEvent::Error carrying the child's exit status and captured stderr when a completed turn exited non-zero without producing any session events, and wire it into the claude, codex, grok, pi, hermes, and muse headless turn loops. EventEmitter now counts emitted session events so adapters can tell a failed turn that produced output apart from one that died silently at launch. Closes #1208
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1208.
Problem
When a wrapper adapter's harness child exits immediately with a fatal configuration error (e.g. grok rejecting
--allow "MultiEdit(...)"withunknown tool prefix: MultiEdit), the error only reached the adapter's stderr log viaemit.log. The session showedawaiting inputwith an empty transcript — indistinguishable from a hung agent until someone grepped the daemon log.Change
adapter-common: newStderrTail(bounded, last 20 lines) +spawn_stderr_tail(same asspawn_stderr_logbut retains the tail), andemit_launch_failure_if_silent— after a completed turn, if the child exited non-zero and no session event was emitted since spawn, emit aSessionEvent::Errorcarrying the exit status and captured stderr (or a pointer to the daemon log when stderr was empty).protocol:EventEmitternow counts emitted session events (events_emitted(), shared across clones) so adapters can tell a failed turn that produced output apart from one that died silently at launch. Turns that produced real output and then failed are left alone — the transcript already shows what happened.claude,codex,grok,pi,hermes,muse. OnlyTurnOutcome::Completedturns are checked — interrupted/stopped turns are killed by us and would misreport the kill as a launch failure. Codex keeps its existingERROR:-line detection as the primary path; this is the fallback. Hermes keeps surfacing stdout first and falls back to the stderr tail when stdout was empty.Regression test
crates/adapter-commonlaunch_failure_tests: spawns a real child that prints the exact failure from #1204 to stderr and exits 2, drives it throughdrive_turn, and asserts exactly oneSessionEvent::Errorcontaining the stderr is emitted. Negative cases: clean exit, and non-zero exit after real output (no synthetic error). Plus tail-bounding and empty-stderr message tests.Not in scope
run_pty) launch failures — the PTY path renders the child's dying words in the terminal, and the issue's repro is headless.