Hmbown/CodeWhale · error
interactive key entry requires a terminal; use `--api-key-st
Error message
interactive key entry requires a terminal; use `--api-key-stdin` for piped input
What it means
Hidden (masked) API-key entry requires an interactive terminal on stdin, because it renders a secure no-echo prompt via console::Term. This bail fires when the key-add/login path falls back to the hidden prompt while stdin is not a TTY - typically in pipes, scripts, and CI - and tells you to use the explicit stdin mode instead.
Source
Thrown at crates/cli/src/cloud.rs:974
.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 0c42157ee5)
Solutions
- Use the explicit pipe mode: printf '%s' "$KEY" | codewhale ... --api-key-stdin
- Or set the provider's env var (e.g. DEEPSEEK_API_KEY) instead of interactive entry
- For real interactive use, allocate a TTY: run without redirection, or ssh -t / docker run -it
- Rewrite automation to pass the key via config or secret store rather than a prompt
Example fix
# before codewhale cloud login --provider deepseek # stdin not a TTY -> error # after printf '%s' "$DEEPSEEK_API_KEY" | codewhale cloud login --provider deepseek --api-key-stdin
Defensive patterns
Strategy: fallback
Validate before calling
use std::io::IsTerminal;
if !std::io::stdin().is_terminal() {
// fall back to explicit pipe mode instead of the hidden prompt
let key = read_key_from_stdin()?; // --api-key-stdin path
} else {
let key = read_key_hidden("deepseek")?;
} Try / catch
match read_key_hidden(provider) {
Ok(k) => k,
Err(e) if e.to_string().contains("requires a terminal") => read_key_from_stdin()?,
Err(e) => return Err(e),
} Prevention
- Branch automation on std::io::stdin().is_terminal() and choose stdin mode up front
- Always pass --api-key-stdin in CI/docker (no -t) contexts
- Prefer env vars or the secret store for non-interactive provisioning
When it happens
Trigger: Running a command that prompts for the key (no --api-key-stdin) with stdin redirected: `codewhale cloud login ... < file`, inside `curl ... | sh` style pipelines, in CI runners, or under ssh -T where no TTY is allocated.
Common situations: CI/CD jobs provisioning credentials; Docker containers without -t; scripts meant to be interactive being run non-interactively; automation reusing an interactive command.
Related errors
- API key input is unexpectedly large
- API key must be {MIN_API_KEY_BYTES}-{MAX_API_KEY_BYTES} UTF-
- API key contains invalid control characters
- key label must contain 1-{MAX_KEY_LABEL_CHARS} characters
- No API key provided. Pass --api-key or pipe one via stdin.
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/8fcdf553183fca25.
Report an issue: GitHub.