{"record":{"id":"8fcdf553183fca25","repo":"Hmbown/CodeWhale","slug":"interactive-key-entry-requires-a-terminal-use","errorCode":null,"errorMessage":"interactive key entry requires a terminal; use `--api-key-stdin` for piped input","messagePattern":"interactive key entry requires a terminal; use `--api-key-stdin` for piped input","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud.rs","lineNumber":1245,"sourceCode":"        .take(MAX_API_KEY_STDIN_BYTES + 1)\n        .read_to_end(&mut bytes)\n        .context(\"failed to read API key from stdin\")?;\n    parse_key_input(bytes)\n}\n\nfn parse_key_input(bytes: Vec<u8>) -> Result<String> {\n    if bytes.len() as u64 > MAX_API_KEY_STDIN_BYTES {\n        bail!(\"API key input is unexpectedly large\");\n    }\n    let value = String::from_utf8(bytes).context(\"API key from stdin is not valid UTF-8\")?;\n    let value = value.trim().to_string();\n    validate_api_key(&value)?;\n    Ok(value)\n}\n\nfn read_key_hidden(provider: &str) -> Result<String> {\n    if !io::stdin().is_terminal() {\n        bail!(\"interactive key entry requires a terminal; use `--api-key-stdin` for piped input\");\n    }\n    let term = console::Term::stderr();\n    term.write_str(&format!(\"Enter {provider} API key: \"))\n        .context(\"failed to write API key prompt\")?;\n    let value = term\n        .read_secure_line()\n        .context(\"failed to read API key securely\")?;\n    term.write_line(\"\").ok();\n    let value = value.trim().to_string();\n    validate_api_key(&value)?;\n    Ok(value)\n}\n\nfn json_body(value: &impl Serialize) -> Result<Vec<u8>> {\n    serde_json::to_vec(value).context(\"failed to encode Codewhale account request\")\n}\n\nfn expect_json<T: DeserializeOwned>(response: CloudResponse, statuses: &[u16]) -> Result<T> {","sourceCodeStart":1227,"sourceCodeEnd":1263,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud.rs#L1227-L1263","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Use the non-interactive path: pipe the key with --api-key-stdin (e.g. `codewhale ... --api-key-stdin < key.txt`)","Run the command in an interactive terminal","For ssh, allocate a TTY: `ssh -t host codewhale login`","In CI, store the key in a secret and pass it via --api-key-stdin"],"exampleFix":"// before\nread -r KEY; codewhale account api-keys import   # stdin not a TTY\n// after\nprintf '%s' \"$KEY\" | codewhale account api-keys import --api-key-stdin","handlingStrategy":"fallback","validationCode":"// shell: detect non-TTY and switch input mode\nif [ -t 0 ]; then\n  codewhale account api-keys import\nelse\n  printf '%s' \"$KEY\" | codewhale account api-keys import --api-key-stdin\nfi","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In scripts and CI, always use --api-key-stdin or --api-key","Use `ssh -t` when an interactive prompt is needed over ssh","Check `[ -t 0 ]` before invoking interactive commands"],"tags":["cli","tty","stdin"],"backgroundTag":"missing-tty-interactive-input","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}