You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found live during #6388's fix verification (fixing AppChannel never forwarding elicit() to the real CliChannel/TuiChannel implementations). Once elicitation requests actually reach CliChannel::elicit() (previously unreachable — see #6388), a second, separate bug surfaces: CliChannel's persistent background chat-input reader (run_tty_reader, crates/zeph-channels/src/cli.rs:261-269) races with elicit()'s own blocking per-field readline (crates/zeph-channels/src/cli.rs:545, via spawn_blocking) for the same stdin/terminal, with no coordination between the two concurrent readers.
This bug was structurally unreachable before #6388 landed — elicit() was never invoked at all in the real binary, so the race could never trigger. It may have existed silently since MCP elicitation shipped.
Build a minimal stdio MCP server that sends a real elicitation/create request mid-tools/call (the CI-1411 fixture server works: .local/testing/scripts/fake_mcp_elicit_server_ci1411.py + .local/config/ci1411-elicitation.toml).
Run cargo run --features full -- --config .local/config/ci1411-elicitation.toml (plain CLI).
Trigger the tool call that causes the elicitation request; the [MCP server '...' is requesting input] banner and confirm [true/false]: prompt appear correctly.
Type a response at the prompt.
Observe: the typed input is consumed by the background chat-input reader instead of the elicitation readline, and the round trip ends in a client-side elicitation timeout (~60s) instead of completing with the typed value.
Expected Behavior
While an elicitation prompt is active, stdin input should route to the elicitation readline exclusively; the background chat-input reader should yield/pause until the elicitation response is resolved.
Actual Behavior
Both readers compete for stdin concurrently with no coordination, so the elicitation response is frequently/always stolen by the wrong reader, causing a 60s client-side timeout instead of a completed round trip.
#6388 (where this was incidentally discovered during live fix-verification, not caused by that fix — it made the pre-existing race reachable for the first time)
Description
Found live during #6388's fix verification (fixing
AppChannelnever forwardingelicit()to the realCliChannel/TuiChannelimplementations). Once elicitation requests actually reachCliChannel::elicit()(previously unreachable — see #6388), a second, separate bug surfaces:CliChannel's persistent background chat-input reader (run_tty_reader,crates/zeph-channels/src/cli.rs:261-269) races withelicit()'s own blocking per-field readline (crates/zeph-channels/src/cli.rs:545, viaspawn_blocking) for the same stdin/terminal, with no coordination between the two concurrent readers.This bug was structurally unreachable before #6388 landed —
elicit()was never invoked at all in the real binary, so the race could never trigger. It may have existed silently since MCP elicitation shipped.Reproduction Steps
AppChannel::elicit()actually forwards toCliChannel::elicit().elicitation/createrequest mid-tools/call(the CI-1411 fixture server works:.local/testing/scripts/fake_mcp_elicit_server_ci1411.py+.local/config/ci1411-elicitation.toml).cargo run --features full -- --config .local/config/ci1411-elicitation.toml(plain CLI).[MCP server '...' is requesting input]banner andconfirm [true/false]:prompt appear correctly.Expected Behavior
While an elicitation prompt is active, stdin input should route to the elicitation readline exclusively; the background chat-input reader should yield/pause until the elicitation response is resolved.
Actual Behavior
Both readers compete for stdin concurrently with no coordination, so the elicitation response is frequently/always stolen by the wrong reader, causing a 60s client-side timeout instead of a completed round trip.
Environment
crates/zeph-channels/src/cli.rs)See Also
#6388 (where this was incidentally discovered during live fix-verification, not caused by that fix — it made the pre-existing race reachable for the first time)