{"record":{"id":"4136641efd7cb20f","repo":"Hmbown/CodeWhale","slug":"key-label-must-contain-1-max-key-label-chars-cha","errorCode":null,"errorMessage":"key label must contain 1-{MAX_KEY_LABEL_CHARS} characters","messagePattern":"key label must contain 1-(.+?) characters","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/cli/src/cloud.rs","lineNumber":895,"sourceCode":"\nfn validate_api_key(key: &str) -> Result<()> {\n    let bytes = key.len();\n    if bytes < MIN_API_KEY_BYTES || bytes as u64 > MAX_API_KEY_BYTES {\n        bail!(\"API key must be {MIN_API_KEY_BYTES}-{MAX_API_KEY_BYTES} UTF-8 bytes\");\n    }\n    if key.chars().any(is_ascii_control) {\n        bail!(\"API key contains invalid control characters\");\n    }\n    Ok(())\n}\n\nfn validate_label(label: &str) -> Result<String> {\n    let label = label.split_whitespace().collect::<Vec<_>>().join(\" \");\n    if label.is_empty()\n        || label.chars().count() > MAX_KEY_LABEL_CHARS\n        || label.chars().any(is_ascii_control)\n    {\n        bail!(\"key label must contain 1-{MAX_KEY_LABEL_CHARS} characters\");\n    }\n    Ok(label)\n}\n\nfn is_ascii_control(character: char) -> bool {\n    character <= '\\u{001f}' || character == '\\u{007f}'\n}\n\nfn resolve_local_key(\n    config: &ConfigStore,\n    secrets: &Secrets,\n    provider: CloudProvider,\n) -> Result<Option<String>> {\n    let kind = provider.local_kind();\n    let provider_config = config.config.providers.for_provider(kind);\n    let from_config = provider_config.api_key.clone().or_else(|| {\n        (kind == ProviderKind::Deepseek)\n            .then(|| config.config.api_key.clone())","sourceCodeStart":877,"sourceCodeEnd":913,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/cli/src/cloud.rs#L877-L913","documentation":"Key labels (e.g. for `codewhale cloud key add --label`) are normalized by collapsing whitespace, then must be 1-80 characters with no control characters. This bail fires when the normalized label is empty, longer than 80 chars, or contains control bytes - for example a label made only of spaces collapses to empty and is rejected.","triggerScenarios":"Passing --label '   ' (whitespace-only), a 100+ character description, or a label with embedded tabs/newlines; the value is whitespace-collapsed first so 'a\\n\\nb' passes but ' \\t ' does not.","commonSituations":"Scripted key creation where the label variable is empty or unset (expands to blank); labels copied from ticket titles that exceed 80 chars; multi-line paste into a label prompt.","solutions":["Provide a short non-empty label (default to something like the hostname or date if scripting: --label \"${LABEL:-ci-$(date +%F)}\")","Trim the label to <=80 characters before passing it","Remove newlines/tabs from scripted labels: --label \"$(echo \"$LABEL\" | tr '\\n' ' ')\"","Check for unset variables in scripts (set -u) so labels never silently become empty"],"exampleFix":"# before\ncodewhale cloud key add --label \"\"   # empty after collapse -> error\n# after\ncodewhale cloud key add --label \"${LABEL:-laptop-$(date +%F)}\"","handlingStrategy":"validation","validationCode":"const MAX_KEY_LABEL_CHARS: usize = 80;\n\nfn label_ok(raw: &str) -> bool {\n    let normalized = raw.split_whitespace().collect::<Vec<_>>().join(\" \");\n    !normalized.is_empty()\n        && normalized.chars().count() <= MAX_KEY_LABEL_CHARS\n        && !normalized.chars().any(|c| c <= '\\u{001f}' || c == '\\u{007f}')\n}","typeGuard":"fn is_valid_label(label: &str) -> bool {\n    let n = label.split_whitespace().collect::<Vec<_>>().join(\" \");\n    !n.is_empty() && n.chars().count() <= 80\n        && !n.chars().any(|c| c <= '\\u{001f}' || c == '\\u{007f}')\n}","tryCatchPattern":null,"preventionTips":["Default labels in scripts: --label \"${LABEL:-ci-$(date +%F)}\"","Truncate long ticket titles to 80 chars before passing","set -u in scripts so label variables cannot silently be empty"],"tags":["cloud","api-key","label","validation"],"backgroundTag":"label-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}