{"record":{"id":"e14b922c58b2b144","repo":"Hmbown/CodeWhale","slug":"refusing-terminal-output-pipe-credential-handoff","errorCode":null,"errorMessage":"refusing terminal output; pipe credential handoff to the intended local client","messagePattern":"refusing terminal output; pipe credential handoff to the intended local client","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/credential_handoff.rs","lineNumber":15,"sourceCode":"use super::{runtime_overrides_for_provider, xai_auth_diagnostics};\nuse anyhow::{Context, Result, bail, ensure};\nuse codewhale_config::{\n    CliRuntimeOverrides, ConfigStore, ProviderKind, RuntimeApiKeySource,\n    auth_mode_uses_kimi_imported_token,\n};\nuse codewhale_secrets::Secrets;\nuse std::io::{ErrorKind, Write};\nuse zeroize::Zeroizing;\n\nconst TERMINAL_REFUSAL: &str =\n    \"refusing terminal output; pipe credential handoff to the intended local client\";\n\npub(crate) fn prepare_stdout(stdout_is_terminal: bool) -> Result<()> {\n    ensure!(!stdout_is_terminal, TERMINAL_REFUSAL);\n    #[cfg(unix)]\n    // SAFETY: this one-shot CLI exits before another command can inherit it.\n    unsafe {\n        let _ = libc::signal(libc::SIGPIPE, libc::SIG_IGN);\n    }\n    Ok(())\n}\n\npub(crate) fn resolve_api_key(\n    store: &ConfigStore,\n    secrets: &Secrets,\n    provider: ProviderKind,\n    runtime_overrides: &CliRuntimeOverrides,\n) -> Result<String> {\n    let resolved = store.config.resolve_runtime_options_with_secrets(\n        &runtime_overrides_for_provider(runtime_overrides, provider),\n        secrets,\n    );","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/credential_handoff.rs#L1-L33","documentation":"prepare_stdout refuses to write credential-handoff output when stdout is a terminal. The credential handoff is designed to be piped to a local client; printing a secret to an interactive terminal would leak it to scrollback and shoulder-surfers, so it fails loudly instead. It also ignores SIGPIPE so a downstream pipe closing does not kill the process mid-write.","triggerScenarios":"Running the credential handoff subcommand directly in a shell without redirecting stdout, e.g. `codewhale handoff ...` with no pipe or file redirection.","commonSituations":"Developer testing the handoff command interactively to 'see' the key; documentation examples followed without the pipe; debugging why a local client is not receiving credentials.","solutions":["Pipe the output to the intended client instead of the terminal, e.g. `codewhale handoff ... | client-ingest`.","Redirect to a file for inspection only if you accept the risk: `codewhale handoff ... > /tmp/key` (prefer a pipe).","If you are wiring up a local client, invoke the handoff from that client with a captured stdout pipe (Stdio::piped)."],"exampleFix":"// before\n$ codewhale credential-handoff   # refused: stdout is a terminal\n// after\n$ codewhale credential-handoff | my-client --ingest-key","handlingStrategy":"validation","validationCode":"// caller-side check before invoking the handoff\nlet stdout_is_tty = unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 };\nif stdout_is_tty {\n    eprintln!(\"redirect stdout to the client pipe before running handoff\");\n}","typeGuard":null,"tryCatchPattern":"match handoff_secret_line() {\n    Err(e) if e.to_string().contains(\"refusing terminal output\") => {\n        eprintln!(\"pipe the output to your local client, e.g. cmd | client --ingest-key\");\n    }\n    Err(e) => return Err(e),\n    Ok(v) => Ok(v),\n}","preventionTips":["Always invoke the handoff with stdout piped (Stdio::piped from the client) or redirected.","Never run secret-handoff commands bare in an interactive shell.","Automate the pipe in your client's setup script so there is no terminal path.","Treat the refusal as a security guardrail, not a bug."],"tags":["security","credentials","cli","pipe"],"backgroundTag":"missing-credentials","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"}