Kuberwastaken/claurst · error

Stdin closed unexpectedly

Error message

Stdin closed unexpectedly

What it means

The manual-paste branch of the auth-code wait loop maps a closed stdin stream to this error. paste_rx (the stdin line stream) ended with None before the user pasted the '<code>#<state>' string, so there is no code to extract.

Solutions

  1. Run the login command in an interactive terminal with a live stdin.
  2. Paste the full '<code>#<state>' value from the browser within 120 seconds instead of closing stdin.
  3. Avoid piping the login command; if piped, feed the code via the piped input explicitly.
  4. Use the loopback callback path (allow the localhost redirect_uri) so stdin is not required.

Example fix

// before
$ claurst login < /dev/null
// after
$ claurst login   # run interactively, paste the code#state line
Defensive patterns

Strategy: validation

Validate before calling

// only take the interactive paste path when stdin is a TTY
if !stdin_is_tty() {
    bail!("manual code paste requires an interactive terminal");
}

Try / catch

// handle EOF distinctly so users know stdin died
match paste_rx.await {
    Ok(Some(line)) => Ok(line),
    Ok(None) | Err(_) => Err(anyhow!("Stdin closed unexpectedly")),
}

Prevention

When it happens

Trigger: In the tokio::select! in wait_for_auth_code_impl, the paste_rx branch completes with None: stdin reached EOF (user pressed Ctrl-D, piped input exhausted, or stdin detached in a headless/CI environment) within the 120-second window.

Common situations: Running the login flow in CI or a non-interactive shell where stdin is /dev/null; piping the command; pressing Ctrl-D instead of pasting; terminal emulator detaching mid-flow.

Related errors


AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/95dba7ad0283729d. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/cli/src/oauth_flow.rs:537

            let trimmed = line.trim().to_string();
            if !trimmed.is_empty() {
                let _ = paste_tx.send(trimmed);
            }
        }
    });

    tokio::select! {
        result = cb_rx => {
            // Loopback callback: code came clean from the query string and was
            // authorized against the localhost redirect_uri.
            result
                .unwrap_or_else(|_| Err(anyhow::anyhow!("Callback server dropped")))
                .map(|code| (code, false))
        }
        code = paste_rx => {
            // Manual paste: the page hands back "<code>#<state>"; keep only the
            // code part. This path authorized against MANUAL_REDIRECT_URL.
            let raw = code.map_err(|_| anyhow::anyhow!("Stdin closed unexpectedly"))?;
            let code_only = raw.split('#').next().unwrap_or(&raw).trim().to_string();
            Ok((code_only, true))
        }
        _ = tokio::time::sleep(Duration::from_secs(120)) => {
            bail!("Authentication timed out after 120 seconds")
        }
    }
}

View on GitHub (pinned to b0637c97ec)