{"record":{"id":"8f82c181c2635bd2","repo":"decolua/9router","slug":"failed-to-connect-to-server","errorCode":null,"errorMessage":"Failed to connect to server","messagePattern":"Failed to connect to server","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/github.js","lineNumber":214,"sourceCode":"      \n      const response = await fetch(`${server}/api/cli/providers/github`, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n          Authorization: `Bearer ${token}`,\n          \"X-User-Id\": userId,\n        },\n        body: JSON.stringify({\n          accessToken: authResult.accessToken,\n          copilotToken: authResult.copilotToken,\n          userInfo: authResult.userInfo,\n          copilotTokenInfo: authResult.copilotTokenInfo,\n        }),\n      });\n      \n      if (!response.ok) {\n        const errorData = await response.json();\n        throw new Error(errorData.error || \"Failed to connect to server\");\n      }\n      \n      spinner.succeed(\"GitHub Copilot connected successfully!\");\n      console.log(`\\nConnected as: ${authResult.userInfo.login}`);\n    } catch (error) {\n      const { error: showError } = await import(\"../utils/ui.js\");\n      showError(`GitHub connection failed: ${error.message}`);\n      throw error;\n    }\n  }\n}\n","sourceCodeStart":196,"sourceCodeEnd":226,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/github.js#L196-L226","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (server response without `error` field loses the real cause)\nif (!response.ok) {\n  const errorData = await response.json();\n  throw new Error(errorData.error || \"Failed to connect to server\");\n}\n// after (surface status and raw body for diagnosis)\nif (!response.ok) {\n  const body = await response.text();\n  let msg; try { msg = JSON.parse(body).error; } catch {}\n  throw new Error(msg || `Failed to connect to server (HTTP ${response.status}): ${body.slice(0, 200)}`);\n}","handlingStrategy":"try-catch","validationCode":"// Before calling connect(), verify server reachability and credentials\nconst { server, token, userId } = await getServerCredentials();\nconst health = await fetch(`${server}/api/health`, {\n  headers: { Authorization: `Bearer ${token}` },\n}).catch(() => null);\nif (!health || !health.ok) throw new Error(`Server ${server} unreachable or auth rejected`);","typeGuard":"function hasServerError(data) {\n  return data !== null && typeof data === \"object\" && typeof data.error === \"string\";\n}","tryCatchPattern":"try {\n  await githubService.connect();\n} catch (err) {\n  if (err.message === \"Failed to connect to server\" || /GitHub connection failed/.test(err.message)) {\n    console.error(\"Server rejected credential upload — check server URL, JWT freshness, and server logs:\", err.message);\n  } else { throw err; }\n}","preventionTips":["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."],"tags":["http","network","oauth","server"],"backgroundTag":"http-request-failed","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}