{"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":4008,"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":3990,"sourceCodeEnd":4026,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/lib.rs#L3990-L4026","documentation":"The interactive API-key reader prints `Enter API key for <slot>:` (or reads quietly when stdin is not a terminal), trims the line, and rejects an empty result. An empty key would silently produce a broken provider configuration, so Codewhale fails fast with `empty API key provided` instead of storing nothing.","triggerScenarios":"Pressing Enter without typing at the API-key prompt; pasting only whitespace/newlines; a terminal where the paste lands after the read; piping blank input into the interactive path.","commonSituations":"Password managers failing to auto-fill the hidden prompt; users intending to skip key entry; trailing-newline-only stdin from a wrapper script; keys copied with only whitespace.","solutions":["Re-run and paste a real, non-empty API key for the slot named in the prompt","Provide the key non-interactively: pipe it (`printf '%s\\n' \"$KEY\" | codewhale auth api-key ...`) or use the provider's env var","Check the key in the provider console and re-copy it without stray whitespace"],"exampleFix":"# before\nEnter API key for deepseek: <Enter>\n# empty API key provided\n\n# after\nEnter API key for deepseek: sk-********","handlingStrategy":"validation","validationCode":"#!/usr/bin/env bash\nkey=\"${DEEPSEEK_API_KEY:-}\"\n[ -n \"${key// /}\" ] || { read -rs -p \"API key: \" key; echo; }\n[ -n \"${key// /}\" ] || { echo \"empty API key\" >&2; exit 1; }\nprintf '%s\\n' \"$key\" | codewhale auth api-key --provider deepseek","typeGuard":"fn is_valid_api_key(s: &str) -> bool {\n    !s.trim().is_empty()\n}","tryCatchPattern":null,"preventionTips":["Pre-validate `key.trim()` is non-empty before piping or prompting","Pull keys from a real secret store; never assume the env var is populated"],"tags":["api-key","auth","empty-input","prompt","cli"],"backgroundTag":"missing-api-key","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}