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

  1. Pipe the output to the intended client instead of the terminal, e.g. `codewhale handoff ... | client-ingest`.
  2. Redirect to a file for inspection only if you accept the risk: `codewhale handoff ... > /tmp/key` (prefer a pipe).
  3. 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

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


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)