Hmbown/CodeWhale · error
refusing terminal output; pipe credential handoff to the…
Error message
refusing terminal output; pipe credential handoff to the intended local client
What it means
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.
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).
Example fix
// before $ codewhale credential-handoff # refused: stdout is a terminal // after $ codewhale credential-handoff | my-client --ingest-key
Defensive patterns
Strategy: validation
Validate before calling
// caller-side check before invoking the handoff
let stdout_is_tty = unsafe { libc::isatty(libc::STDOUT_FILENO) == 1 };
if stdout_is_tty {
eprintln!("redirect stdout to the client pipe before running handoff");
} Try / catch
match handoff_secret_line() {
Err(e) if e.to_string().contains("refusing terminal output") => {
eprintln!("pipe the output to your local client, e.g. cmd | client --ingest-key");
}
Err(e) => return Err(e),
Ok(v) => Ok(v),
} Prevention
- 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.
When it happens
Trigger: Running the credential handoff subcommand directly in a shell without redirecting stdout, e.g. `codewhale handoff ...` with no pipe or file redirection.
Common situations: 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.
Related errors
- credential handoff could not write to stdout
- bundle URLs may not include credentials
- Codewhale account API base URL must not contain credentials
- `codewhale account` does not accept the global `--api-key`…
- Codewhale-owned credential file DACL is not…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e14b922c58b2b144.
Report an issue: GitHub.
Appendix: source
Thrown at crates/cli/src/credential_handoff.rs:15
use super::{runtime_overrides_for_provider, xai_auth_diagnostics};
use anyhow::{Context, Result, bail, ensure};
use codewhale_config::{
CliRuntimeOverrides, ConfigStore, ProviderKind, RuntimeApiKeySource,
auth_mode_uses_kimi_imported_token,
};
use codewhale_secrets::Secrets;
use std::io::{ErrorKind, Write};
use zeroize::Zeroizing;
const TERMINAL_REFUSAL: &str =
"refusing terminal output; pipe credential handoff to the intended local client";
pub(crate) fn prepare_stdout(stdout_is_terminal: bool) -> Result<()> {
ensure!(!stdout_is_terminal, TERMINAL_REFUSAL);
#[cfg(unix)]
// SAFETY: this one-shot CLI exits before another command can inherit it.
unsafe {
let _ = libc::signal(libc::SIGPIPE, libc::SIG_IGN);
}
Ok(())
}
pub(crate) fn resolve_api_key(
store: &ConfigStore,
secrets: &Secrets,
provider: ProviderKind,
runtime_overrides: &CliRuntimeOverrides,
) -> Result<String> {
let resolved = store.config.resolve_runtime_options_with_secrets(
&runtime_overrides_for_provider(runtime_overrides, provider),
secrets,
);View on GitHub (pinned to 73e0f67d83)