fix(desktop): close stdin on CLI login probes so they cannot hang readiness - #5411
fix(desktop): close stdin on CLI login probes so they cannot hang readiness#5411NedMalki-Chief wants to merge 1 commit into
Conversation
…diness login_probe spawns the harness auth probe (claude auth status / codex login status) via Command::output(), which pipes stdout and stderr but leaves stdin inherited. A GUI-launched desktop has no console, and CLIs that sniff stdin for piped input - Claude Code does - block on it indefinitely. Observed on Windows 11: a claude auth status probe child alive for 420s and never exiting, while the same command with stdin closed exits in about 1s with valid logged-in JSON. Because agent_readiness gates on this probe, the hang is load-bearing: agent creation sits on "Saving..." indefinitely, the desktop respawns the probe which hangs again, and a newly created Claude Code agent can never leave NotReady, so its runtime never subscribes and mentions to it are dropped. A hung probe is strictly worse than a failed one - failure produces a CliLogin requirement the UI can surface, while a hang wedges every flow behind it. Fix: spawn the probe with stdin explicitly null. Signed-off-by: Ned Malki <ned@ottomato.ai>
wesbillman
left a comment
There was a problem hiding this comment.
Reviewing on Wes's behalf.
The code change may be a valid Windows/Claude compatibility workaround, but the stated mechanism is incorrect and the current title/comment overpromise what it fixes.
std::process::Command::output() does not leave stdin inherited. Rust 1.95 (the toolchain pinned by this repository) documents:
Stdin is not inherited from the parent and any attempt by the child process to read from the stdin stream will result in the stream immediately closing.
See library/std/src/process.rs:1061 and the implementation in library/std/src/sys/process/mod.rs:48-52, which creates a stdin pipe and drops the parent end after spawn. This applies on Windows too. Therefore this patch changes the child's stdin handle from an already-closed anonymous pipe to the OS null device; it does not change inherited stdin to closed stdin.
That distinction can still matter if Claude Code branches incorrectly on Windows handle type before reading EOF, and the reported A/B behavior is credible. Stdio::null() is semantically safe for a noninteractive status probe and is consistent with the sibling discovery probe at desktop/src-tauri/src/managed_agents/discovery.rs:1013-1022. But the PR needs to describe the actual compatibility behavior rather than a false standard-library premise.
Also, this does not make readiness unable to hang. output() still has no deadline; network, credential-store, descendant-pipe, or internal CLI stalls remain unbounded. Please:
- Correct the title, PR body, commit message, and source comment to say that Windows Claude was observed hanging with Rust's default closed-pipe stdin but completing with
NUL, so the probe explicitly uses the null device as a compatibility workaround. - Narrow the “cannot hang readiness” claim unless this path also gains a real cross-platform timeout.
- Provide the exact A/B reproduction through the same Rust
Command::output()path (default versus.stdin(Stdio::null())), with CLI version. Shell redirection alone does not establish the claimed default handle behavior.
A proper deadline can be separate work. Do not copy the existing discovery timeout blindly: its Windows timeout branch does not kill the child before joining the wait thread (discovery.rs:1054-1069), so it can itself remain blocked.
Problem
login_probe(desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs) spawns the harness auth probe —claude auth status/codex login status— viaCommand::output(). That pipes stdout/stderr but leaves stdin inherited. A GUI-launched desktop has no console, and CLIs that sniff stdin for piped input (Claude Code does) block on it indefinitely.Measured on Windows 11:
claude auth statusas spawned by the desktop</dev/null)Why it matters more than it looks
agent_readinessgates on this probe, so the hang is load-bearing:NotReady→ the runtime never reaches its relay subscribe → mentions to a newly created agent are silently dropped. Existing agents that passed readiness before the hang keep working, which makes this look like agent-specific corruption rather than what it is.A hung probe is strictly worse than a failed one: failure yields a
CliLoginrequirement the UI can surface, while a hang wedges every flow queued behind it.Fix
Spawn the probe with
stdin(Stdio::null()). One call site; the login/auth CLIs never legitimately need interactive stdin for astatuscheck.Verification
cargo check --libon the desktop crate: clean (warning count unchanged frommain).Related but separate: #5379 (model-probe timeout), #5393 (MCP credential channel for empty
mcp_command). This PR is deliberately single-purpose.Commit is DCO signed-off.