{"record":{"id":"86d2687f23022ac4","repo":"Hmbown/CodeWhale","slug":"unknown-scope-codewhale-api-keys-accept-only","errorCode":null,"errorMessage":"unknown scope `{}`; Codewhale API keys accept only {}","messagePattern":"unknown scope `(.+?)`; Codewhale API keys accept only (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/cli/src/cloud/machine.rs","lineNumber":830,"sourceCode":"}\n\n/// Normalize `--scope` into the closed set.\n///\n/// An omitted `--scope` means every scope, and is sent explicitly rather than\n/// left to a server default: a key minted by this CLI should carry exactly the\n/// scopes the CLI's own help promised, whatever the control plane's default is\n/// this week.\npub(crate) fn validate_scopes(scopes: &[String]) -> Result<Option<Vec<String>>> {\n    if scopes.is_empty() {\n        return Ok(Some(\n            SCOPES.iter().map(|scope| (*scope).to_string()).collect(),\n        ));\n    }\n    let mut normalized = Vec::new();\n    for scope in scopes {\n        let scope = scope.trim();\n        if !SCOPES.contains(&scope) {\n            bail!(\n                \"unknown scope `{}`; Codewhale API keys accept only {}\",\n                printable(scope),\n                SCOPES.join(\", \")\n            );\n        }\n        if !normalized.iter().any(|existing| existing == scope) {\n            normalized.push(scope.to_string());\n        }\n    }\n    Ok(Some(normalized))\n}\n\n/// The 24-hex key id, checked locally.\n///\n/// This is a paste check, not an existence check: the server answers 404\n/// identically for a malformed id, an unknown id, and another account's id, so\n/// nothing here can or should try to distinguish them.\npub(crate) fn validate_key_id(id: &str) -> Result<&str> {","sourceCodeStart":812,"sourceCodeEnd":848,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/cli/src/cloud/machine.rs#L812-L848","documentation":"Scope parsing for Codewhale API keys accepts only the fixed SCOPES allowlist. Each scope string is trimmed and checked against the list; anything else bails listing the accepted scopes. Codewhale keys deliberately support a small closed scope set, so unknown values are rejected client-side.","triggerScenarios":"Passing a scope argument to an api-keys create/update command that is not in SCOPES — misspelled names, plural/singular mismatches, invented scopes like \"admin\" or \"write:all\", or scopes from another product's convention (crates/cli/src/cloud/machine.rs:830).","commonSituations":"Copying scope names from GitHub/OAuth docs, typos like \"read:sesssions\", assuming wildcard scopes exist, or case differences since the check is exact-match after trim.","solutions":["Run the command with no scopes or read the error's list of accepted scopes","Use only exact strings from SCOPES (check `codewhale account api-keys create --help` for the list)","Fix typos and casing to match the allowlist exactly","Remove any scope imported from another product's naming scheme"],"exampleFix":"// before\ncodewhale account api-keys create --scope admin --scope inference\n// after\ncodewhale account api-keys create --scope inference   # use an exact name from SCOPES","handlingStrategy":"validation","validationCode":"// only allowlist scopes accepted by the CLI\nconst SCOPES: [&str; 2] = [\"inference\", \"sessions\"]; // check --help for the exact list\nfn scopes_ok(requested: &[&str]) -> bool {\n    requested.iter().all(|s| SCOPES.contains(&s.trim()))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Consult `codewhale account api-keys create --help` for the exact scope list","Never copy scope names from other products' documentation","Use exact casing and spelling; matching is exact after trim"],"tags":["cli","scopes","validation"],"backgroundTag":"invalid-enum-value","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"}