{"record":{"id":"5a82d4f18e77f4eb","repo":"zeroclaw-labs/zeroclaw","slug":"failed-to-get-copilot-api-key-status-sanitiz","errorCode":null,"errorMessage":"Failed to get Copilot API key ({status}): {sanitized}. Ensure your GitHub account has an active Copilot subscription.","messagePattern":"Failed to get Copilot API key \\((.+?)\\): (.+?)\\. Ensure your GitHub account has an active Copilot subscription\\.","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-providers/src/copilot.rs","lineNumber":629,"sourceCode":"        let mut request = self.http_client().get(GITHUB_API_KEY_URL);\n        for (header, value) in &Self::COPILOT_HEADERS {\n            request = request.header(*header, *value);\n        }\n        request = request.header(\"Authorization\", format!(\"token {access_token}\"));\n\n        let response = request.send().await?;\n\n        if !response.status().is_success() {\n            let status = response.status();\n            let body = response.text().await.unwrap_or_default();\n            let sanitized = super::sanitize_api_error(&body);\n\n            if status.as_u16() == 401 || status.as_u16() == 403 {\n                let access_token_path = self.token_dir.join(\"access-token\");\n                tokio::fs::remove_file(&access_token_path).await.ok();\n            }\n\n            anyhow::bail!(\n                \"Failed to get Copilot API key ({status}): {sanitized}. \\\n                 Ensure your GitHub account has an active Copilot subscription.\"\n            );\n        }\n\n        let info: ApiKeyInfo = response.json().await?;\n        Ok(info)\n    }\n\n    async fn load_api_key_from_disk(&self) -> Option<ApiKeyInfo> {\n        let path = self.token_dir.join(\"api-key.json\");\n        let data = tokio::fs::read_to_string(&path).await.ok()?;\n        serde_json::from_str(&data).ok()\n    }\n\n    async fn save_api_key_to_disk(&self, info: &ApiKeyInfo) {\n        let path = self.token_dir.join(\"api-key.json\");\n        if let Ok(json) = serde_json::to_string_pretty(info) {","sourceCodeStart":611,"sourceCodeEnd":647,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-providers/src/copilot.rs#L611-L647","documentation":"After a successful GitHub login, the provider exchanges the access token for a Copilot API key at GitHub's Copilot management API. Any non-2xx there produces this error with status and sanitized body; on 401/403 the cached access-token file is deleted first, so the next attempt starts a clean device flow.","triggerScenarios":"exchange_for_api_key gets 401/403 because the GitHub account has no active Copilot subscription or the token lost access; 404/5xx when the Copilot API endpoint moved or is down; any other non-success during the exchange.","commonSituations":"Copilot trial or subscription lapsed; account not provisioned by its organization; GitHub API incident; stale cached token (auto-cleared on 401/403 so a retry re-authenticates).","solutions":["Verify the GitHub account has an active Copilot subscription (github.com/settings/copilot)","Re-run the login - the stale access token was already removed on 401/403, so a fresh device flow starts","Check GitHub status (githubstatus.com) for Copilot API incidents","If your organization manages Copilot, confirm the account is on the entitlement list"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Pre-flight the entitlement before depending on Copilot\nasync fn copilot_entitlement_ok(http: &reqwest::Client, token: &str) -> bool {\n    http.get(GITHUB_API_KEY_URL)\n        .header(\"Authorization\", format!(\"token {token}\"))\n        .send().await\n        .map(|r| r.status().is_success())\n        .unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match copilot.get_api_key().await {\n    Ok(key) => { /* cache it */ }\n    Err(e) if e.to_string().contains(\"active Copilot subscription\") => {\n        // terminal: 401/403 already cleared the cached token;\n        // direct the user to github.com/settings/copilot, do not retry in a loop\n    }\n    Err(e) => Err(e),\n}","preventionTips":["Verify the Copilot subscription before onboarding a user to the copilot provider","On 401/403 let the next call re-run device flow - the stale token is already deleted","Monitor GitHub status during Copilot API incidents instead of hammering retries","For org-managed Copilot, confirm entitlements before users authenticate"],"tags":["github","copilot","subscription","api-error","rust"],"backgroundTag":"copilot-entitlement-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}