{"record":{"id":"0b2287b7487146c3","repo":"Hmbown/CodeWhale","slug":"empty-api-key-provided","errorCode":null,"errorMessage":"empty API key provided","messagePattern":"empty API key provided","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/lib.rs","lineNumber":4381,"sourceCode":"        None => \"n/a\",\n    }\n}\n\nfn prompt_api_key(slot: &str) -> Result<String> {\n    use std::io::{IsTerminal, Write};\n    eprint!(\"Enter API key for {slot}: \");\n    io::stderr().flush().ok();\n    if !io::stdin().is_terminal() {\n        // Non-interactive: read directly without prompting twice.\n        return read_api_key_from_stdin();\n    }\n    let mut buf = String::new();\n    io::stdin()\n        .read_line(&mut buf)\n        .context(\"failed to read API key from stdin\")?;\n    let key = buf.trim().to_string();\n    if key.is_empty() {\n        bail!(\"empty API key provided\");\n    }\n    Ok(key)\n}\n\n/// Move plaintext keys from config.toml into the configured secret store.\n/// Hidden in v0.8.8 because the normal setup path is config/env only.\nfn run_auth_migrate(store: &mut ConfigStore, secrets: &Secrets, dry_run: bool) -> Result<()> {\n    let mut migrated: Vec<(ProviderKind, &'static str)> = Vec::new();\n    let mut warnings: Vec<String> = Vec::new();\n    let literal =\n        |value: &String| classify_config_api_key_value(value) == ConfigApiKeyValueKind::Literal;\n\n    for provider in ProviderKind::ALL {\n        let slot = provider_slot(provider);\n        let from_provider_block = store\n            .config\n            .providers\n            .for_provider(provider)","sourceCodeStart":4363,"sourceCodeEnd":4399,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/lib.rs#L4363-L4399","documentation":"The `--api-key-stdin` reader trims the line read from stdin and rejects it when empty, refusing to store a blank API key. The surrounding read errors carry a distinct context, so this bail specifically means input arrived but contained no key.","triggerScenarios":"Piping an empty string, whitespace-only input, or an unset-variable expansion to `codewhale auth set --provider <p> --api-key-stdin`.","commonSituations":"`echo \"$KEY\"` where KEY is unset/empty; secrets manager returning empty in CI; piping a file whose relevant line is blank; copy-paste of a placeholder.","solutions":["Verify the secret source is non-empty (`test -n \"$KEY\"` before piping)","Re-run with the actual key: `printf '%s' \"$KEY\" | codewhale auth set --provider <p> --api-key-stdin`","Use the hidden interactive prompt (`codewhale auth set --provider <p>`) if stdin plumbing is unreliable"],"exampleFix":"// before\necho \"$OPENAI_API_KEY\" | codewhale auth set --provider openai --api-key-stdin  # KEY unset\n// after\n[ -n \"$OPENAI_API_KEY\" ] && printf '%s' \"$OPENAI_API_KEY\" | codewhale auth set --provider openai --api-key-stdin","handlingStrategy":"validation","validationCode":"if [ -z \"${KEY//[[:space:]]/}\" ]; then echo \"key is empty\"; exit 1; fi\nprintf '%s' \"$KEY\" | codewhale auth set --provider \"$PROVIDER\" --api-key-stdin","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Assert secrets are non-empty before piping (`test -n \"$KEY\"`)","Check CI secret bindings actually inject the variable","Avoid echo; use printf '%s' to avoid stray newlines"],"tags":["cli","auth","api-key","empty-input"],"backgroundTag":"empty-required-field","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"}