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.

Solutions

  1. Verify the server is running and reachable at the configured `server` URL (curl `${server}/api/health`).
  2. Re-authenticate against the dashboard to get a fresh Bearer token (JWT may have expired or JWT_SECRET changed).
  3. Confirm server and CLI versions match — the /api/cli/providers/github route must exist server-side.
  4. Check server logs for the actual failure (duplicate connection, quota, DB write failure).
  5. 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

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


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/8f82c181c2635bd2. Report an issue: GitHub.

Appendix: 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)