zeroclaw-labs/zeroclaw · error · anyhow::Error
Failed to get Copilot API key ({status}): {sanitized}. Ensur
Error message
Failed to get Copilot API key ({status}): {sanitized}. Ensure your GitHub account has an active Copilot subscription. What it means
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.
Source
Thrown at crates/zeroclaw-providers/src/copilot.rs:629
let mut request = self.http_client().get(GITHUB_API_KEY_URL);
for (header, value) in &Self::COPILOT_HEADERS {
request = request.header(*header, *value);
}
request = request.header("Authorization", format!("token {access_token}"));
let response = request.send().await?;
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
let sanitized = super::sanitize_api_error(&body);
if status.as_u16() == 401 || status.as_u16() == 403 {
let access_token_path = self.token_dir.join("access-token");
tokio::fs::remove_file(&access_token_path).await.ok();
}
anyhow::bail!(
"Failed to get Copilot API key ({status}): {sanitized}. \
Ensure your GitHub account has an active Copilot subscription."
);
}
let info: ApiKeyInfo = response.json().await?;
Ok(info)
}
async fn load_api_key_from_disk(&self) -> Option<ApiKeyInfo> {
let path = self.token_dir.join("api-key.json");
let data = tokio::fs::read_to_string(&path).await.ok()?;
serde_json::from_str(&data).ok()
}
async fn save_api_key_to_disk(&self, info: &ApiKeyInfo) {
let path = self.token_dir.join("api-key.json");
if let Ok(json) = serde_json::to_string_pretty(info) {View on GitHub (pinned to 88bb9c8533)
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
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-flight the entitlement before depending on Copilot
async fn copilot_entitlement_ok(http: &reqwest::Client, token: &str) -> bool {
http.get(GITHUB_API_KEY_URL)
.header("Authorization", format!("token {token}"))
.send().await
.map(|r| r.status().is_success())
.unwrap_or(false)
} Try / catch
match copilot.get_api_key().await {
Ok(key) => { /* cache it */ }
Err(e) if e.to_string().contains("active Copilot subscription") => {
// terminal: 401/403 already cleared the cached token;
// direct the user to github.com/settings/copilot, do not retry in a loop
}
Err(e) => Err(e),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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).
Related errors
- GitHub device authorization expired
- GitHub auth failed: {error}
- Git channel requires the `channel-git` feature
- {} API error ({status}): {sanitized}
- Timed out waiting for GitHub authorization
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/5a82d4f18e77f4eb.
Report an issue: GitHub.