Hmbown/CodeWhale · error

interactive key entry requires a terminal; use…

Error message

interactive key entry requires a terminal; use `--api-key-stdin` for piped input

What it means

read_key_hidden provides interactive masked key entry via the terminal. It requires stdin to be a TTY; when stdin is not a terminal (piped, redirected, or in CI) it bails with guidance to use --api-key-stdin instead, since the interactive prompt cannot read from a non-TTY stdin securely.

Solutions

  1. Use the non-interactive path: pipe the key with --api-key-stdin (e.g. `codewhale ... --api-key-stdin < key.txt`)
  2. Run the command in an interactive terminal
  3. For ssh, allocate a TTY: `ssh -t host codewhale login`
  4. In CI, store the key in a secret and pass it via --api-key-stdin

Example fix

// before
read -r KEY; codewhale account api-keys import   # stdin not a TTY
// after
printf '%s' "$KEY" | codewhale account api-keys import --api-key-stdin
Defensive patterns

Strategy: fallback

Validate before calling

// shell: detect non-TTY and switch input mode
if [ -t 0 ]; then
  codewhale account api-keys import
else
  printf '%s' "$KEY" | codewhale account api-keys import --api-key-stdin
fi

Prevention

When it happens

Trigger: Running a command that prompts for an API key interactively while stdin is not a TTY — e.g. `echo x | codewhale login ...`, running under a script or CI without `--api-key-stdin`, or inside an IDE/automation harness with redirected stdin (crates/cli/src/cloud.rs:1245).

Common situations: CI pipelines without TTY allocation, `ssh host codewhale login` without -t, scripts invoking the CLI expecting a prompt, or background jobs with stdin closed or redirected from /dev/null.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/8fcdf553183fca25. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/cloud.rs:1245

        .take(MAX_API_KEY_STDIN_BYTES + 1)
        .read_to_end(&mut bytes)
        .context("failed to read API key from stdin")?;
    parse_key_input(bytes)
}

fn parse_key_input(bytes: Vec<u8>) -> Result<String> {
    if bytes.len() as u64 > MAX_API_KEY_STDIN_BYTES {
        bail!("API key input is unexpectedly large");
    }
    let value = String::from_utf8(bytes).context("API key from stdin is not valid UTF-8")?;
    let value = value.trim().to_string();
    validate_api_key(&value)?;
    Ok(value)
}

fn read_key_hidden(provider: &str) -> Result<String> {
    if !io::stdin().is_terminal() {
        bail!("interactive key entry requires a terminal; use `--api-key-stdin` for piped input");
    }
    let term = console::Term::stderr();
    term.write_str(&format!("Enter {provider} API key: "))
        .context("failed to write API key prompt")?;
    let value = term
        .read_secure_line()
        .context("failed to read API key securely")?;
    term.write_line("").ok();
    let value = value.trim().to_string();
    validate_api_key(&value)?;
    Ok(value)
}

fn json_body(value: &impl Serialize) -> Result<Vec<u8>> {
    serde_json::to_vec(value).context("failed to encode Codewhale account request")
}

fn expect_json<T: DeserializeOwned>(response: CloudResponse, statuses: &[u16]) -> Result<T> {

View on GitHub (pinned to 73e0f67d83)