decolua/9router · error · Error
Failed to connect to server
Error message
Failed to connect to server
What it means
Thrown by GitHubService.connect() after successful GitHub device-flow authentication, when the POST of the obtained credentials to `${server}/api/cli/providers/github` returns a non-OK HTTP status. The CLI first tries to read the server's JSON body for a specific `error` message; this generic fallback is used when the server response has no `error` field or is not parseable. It means authentication with GitHub itself worked, but registering the credentials with the 9Router server failed.
Source
Thrown at src/lib/oauth/services/github.js:214
const response = await fetch(`${server}/api/cli/providers/github`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-User-Id": userId,
},
body: JSON.stringify({
accessToken: authResult.accessToken,
copilotToken: authResult.copilotToken,
userInfo: authResult.userInfo,
copilotTokenInfo: authResult.copilotTokenInfo,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to connect to server");
}
spinner.succeed("GitHub Copilot connected successfully!");
console.log(`\nConnected as: ${authResult.userInfo.login}`);
} catch (error) {
const { error: showError } = await import("../utils/ui.js");
showError(`GitHub connection failed: ${error.message}`);
throw error;
}
}
}
View on GitHub (pinned to 90b52e06ff)
Solutions
- Verify the server is running and reachable at the configured `server` URL (curl `${server}/api/health`).
- Re-authenticate against the dashboard to get a fresh Bearer token (JWT may have expired or JWT_SECRET changed).
- Confirm server and CLI versions match — the /api/cli/providers/github route must exist server-side.
- Check server logs for the actual failure (duplicate connection, quota, DB write failure).
- Re-run `9router connect github`; transient 5xx may succeed on retry.
Example fix
// before (server response without `error` field loses the real cause)
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "Failed to connect to server");
}
// after (surface status and raw body for diagnosis)
if (!response.ok) {
const body = await response.text();
let msg; try { msg = JSON.parse(body).error; } catch {}
throw new Error(msg || `Failed to connect to server (HTTP ${response.status}): ${body.slice(0, 200)}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling connect(), verify server reachability and credentials
const { server, token, userId } = await getServerCredentials();
const health = await fetch(`${server}/api/health`, {
headers: { Authorization: `Bearer ${token}` },
}).catch(() => null);
if (!health || !health.ok) throw new Error(`Server ${server} unreachable or auth rejected`); Type guard
function hasServerError(data) {
return data !== null && typeof data === "object" && typeof data.error === "string";
} Try / catch
try {
await githubService.connect();
} catch (err) {
if (err.message === "Failed to connect to server" || /GitHub connection failed/.test(err.message)) {
console.error("Server rejected credential upload — check server URL, JWT freshness, and server logs:", err.message);
} else { throw err; }
} Prevention
- Health-check the server before starting the device-flow login so you don't authenticate then fail on upload.
- Keep the dashboard JWT fresh — re-login when it nears expiry.
- Pin CLI and dashboard versions to compatible releases.
- Watch for the generic fallback message: it means the server gave no `error` body, so check server logs.
When it happens
Trigger: Calling connect() when the server returns 4xx/5xx for /api/cli/providers/github — e.g. expired/invalid Bearer token in getServerCredentials(), missing or wrong X-User-Id, server route not present (version mismatch between CLI and server), or a 500 from the server without an `error` field in the JSON body.
Common situations: Stale JWT after the server was restarted with a different JWT_SECRET; server not running or pointing at the wrong URL in ~/.9router config; CLI version older than the dashboard route; server crashed mid-request returning an HTML error page that fails JSON parsing.
Understand the failure class
Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.
Related errors
- loadCodeAssist failed: HTTP ${response.status} ${errorText.s
- onboardUser HTTP ${response.status}: ${errorText.slice(0, 20
- Failed to save tokens
- Failed to get device code: ${error}
- Token exchange failed: ${error}
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/8f82c181c2635bd2.
Report an issue: GitHub.