zeroclaw-labs/zeroclaw · error · anyhow::Error
GitHub auth failed: {error}
Error message
GitHub auth failed: {error} What it means
The GitHub device-flow token endpoint returned an error code other than the handled slow_down, authorization_pending, and expired_token cases. The raw GitHub error code is embedded in the message; common ones are access_denied (user denied the permission prompt), incorrect_device_code, and incorrect_client_id.
Source
Thrown at crates/zeroclaw-providers/src/copilot.rs:602
.send()
.await?
.json()
.await?;
if let Some(token) = token_response.access_token {
eprintln!("Authentication succeeded.\n");
return Ok(token);
}
match token_response.error.as_deref() {
Some("slow_down") => {
poll_interval += Duration::from_secs(5);
}
Some("authorization_pending") | None => {}
Some("expired_token") => {
anyhow::bail!("GitHub device authorization expired")
}
Some(error) => anyhow::bail!("GitHub auth failed: {error}"),
}
}
anyhow::bail!("Timed out waiting for GitHub authorization")
}
/// Exchange a GitHub access token for a Copilot API key.
async fn exchange_for_api_key(&self, access_token: &str) -> anyhow::Result<ApiKeyInfo> {
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();View on GitHub (pinned to 88bb9c8533)
Solutions
- If the code is access_denied: the user must accept the permission prompt - restart the login
- For any other code, restart the device flow to obtain a fresh device_code
- If it persists, check whether your organization's OAuth app policy blocks the ZeroClaw GitHub app
- Alternatively configure a personal access token directly instead of device flow
Defensive patterns
Strategy: try-catch
Try / catch
match copilot_login().await {
Ok(token) => { /* store token */ }
Err(e) if e.to_string().contains("GitHub auth failed") => {
// read the embedded code: access_denied needs user consent on retry;
// anything else usually needs a fresh device code or a PAT fallback
if e.to_string().contains("access_denied") {
return Err(anyhow::anyhow!("user denied GitHub authorization; retry when ready"));
}
// else: restart the flow once with a fresh device code
}
Err(e) => return Err(e),
} Prevention
- Tell users to click Authorize, not Cancel, on the GitHub permission page
- Never reuse a device code across login attempts
- Check org OAuth app policies before deploying device-flow logins in enterprises
- Keep a PAT-based config path as fallback where device flow is blocked
When it happens
Trigger: User clicks cancel or deny on the GitHub authorization page; the device_code is replayed after expiry; GitHub rejects the OAuth client_id (suspended or blocked app); device flow disabled by policy for the account.
Common situations: User denies the requested scopes; a GitHub org policy restricts the OAuth app; retrying a flow with a stale device code; enterprise-managed accounts blocking third-party device flow.
Related errors
- GitHub device authorization expired
- Timed out waiting for GitHub authorization
- Failed to get Copilot API key ({status}): {sanitized}. Ensur
- Git channel requires the `channel-git` feature
- Google device code request failed ({}): {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9589842a66aadcf0.
Report an issue: GitHub.