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

  1. Verify the GitHub account has an active Copilot subscription (github.com/settings/copilot)
  2. Re-run the login - the stale access token was already removed on 401/403, so a fresh device flow starts
  3. Check GitHub status (githubstatus.com) for Copilot API incidents
  4. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/5a82d4f18e77f4eb. Report an issue: GitHub.