decolua/9router · error · Error
GitHub authentication failed: ${error.message}
Error message
GitHub authentication failed: ${error.message} What it means
GitHubService.authenticate() orchestrates the whole device flow: getDeviceCode → pollAccessToken → getCopilotToken → getUserInfo. Any error thrown by those steps is caught and re-wrapped as `GitHub authentication failed: ${error.message}` at github.js:181. This is a wrapper message — the root cause is the inner error text (e.g. 'Device code expired', 'Access denied', or an HTTP failure).
Source
Thrown at src/lib/oauth/services/github.js:181
const userInfo = await this.getUserInfo(tokenResponse.access_token);
console.log(`\n✅ Successfully authenticated as ${userInfo.login}`);
return {
accessToken: tokenResponse.access_token,
copilotToken: copilotToken.token,
refreshToken: null, // GitHub device flow doesn't return refresh token
expiresIn: copilotToken.expires_at,
userInfo: {
id: userInfo.id,
login: userInfo.login,
name: userInfo.name,
email: userInfo.email,
},
copilotTokenInfo: copilotToken,
};
} catch (error) {
throw new Error(`GitHub authentication failed: ${error.message}`);
}
}
/**
* Connect to server with GitHub credentials
*/
async connect() {
try {
// Authenticate with GitHub
const authResult = await this.authenticate();
// Send credentials to server
const { server, token, userId } = await import("../config/index.js").then(m => m.getServerCredentials());
const spinner = (await import("../utils/ui.js")).spinner("Connecting to server...").start();
const response = await fetch(`${server}/api/cli/providers/github`, {
method: "POST",
headers: {View on GitHub (pinned to 90b52e06ff)
Solutions
- Read the text after 'GitHub authentication failed:' — it contains the inner error; map it to the specific step and fix listed for errors 263–268.
- If the inner message is empty, add temporary logging of the caught error (message + stack) to identify the failing step.
- Re-run authenticate() from a network that can reach github.com and complete the browser authorization promptly.
- Consider preserving the original error (`throw new Error(..., { cause: error })`) for better stack traces.
Example fix
// before
} catch (error) {
throw new Error(`GitHub authentication failed: ${error.message}`);
}
// after
} catch (error) {
throw new Error(`GitHub authentication failed: ${error.message}`, { cause: error });
} Defensive patterns
Strategy: try-catch
Validate before calling
async function canReachGitHub() {
const res = await fetch('https://github.com', { method: 'HEAD' }).catch(() => null);
if (!res) throw new Error('GitHub unreachable — device authentication will fail');
} Type guard
function isAuthResult(r) {
return r !== null && typeof r === 'object' && typeof r.accessToken === 'string' && typeof r.copilotToken === 'string' && r.userInfo?.login;
} Try / catch
try {
const auth = await service.authenticate();
} catch (err) {
const inner = err.cause ?? err;
console.error(`GitHub auth failed at: ${inner.message}`); // unwrap to see the real failing step
if (inner.message === 'Device code expired') return retryWithFreshCode();
if (inner.message === 'Access denied') return promptUserToRetry();
throw err;
} Prevention
- Unwrap the wrapper message to identify the failing step (device code, poll, Copilot token, or user info) before fixing.
- Pre-flight network reachability to github.com before scripted/CI authentication runs.
- Prefer `{ cause: error }` rethrows in your own wrappers so stacks aren't lost.
- Complete browser authorization promptly — the majority of authenticate() failures are user-timing related.
When it happens
Trigger: Any failure inside authenticate(): device-code request failed (263), user never authorized in time (264), user denied (265), unrecognized poll error (266), Copilot token rejected (267), or user-info fetch failed (268).
Common situations: All the underlying situations above; additionally, because the wrapper discards the original stack, developers often see only this generic line when debugging CI or scripted runs where the inner spinner output was suppressed.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- `Device code request failed: ${error}`
- Device code expired
- Access denied
- ${data.error_description || data.error}
- Failed to refresh credentials. Please re-authorize the conne
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/8e9ca4ce5426bf0b.
Report an issue: GitHub.