decolua/9router · error

Failed to save tokens

Error message

Failed to save tokens

What it means

Thrown by IFlowService.saveTokens() when the POST of the exchanged tokens and user profile to the 9Router server endpoint `${server}/api/cli/providers/iflow` returns non-OK. The CLI reads the server's JSON `error` field; this fallback fires when the server gave no specific error. GitHub auth has succeeded at this point — the failure is purely in persisting the connection server-side.

Source

Thrown at src/lib/oauth/services/iflow.js:116

    const response = await fetch(`${server}/api/cli/providers/iflow`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${token}`,
        "X-User-Id": userId,
      },
      body: JSON.stringify({
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token,
        expiresIn: tokens.expires_in,
        apiKey: userInfo.apiKey,
        email: userInfo.email || userInfo.phone,
      }),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.error || "Failed to save tokens");
    }

    return await response.json();
  }

  /**
   * Complete iFlow OAuth flow
   */
  async connect() {
    const spinner = createSpinner("Starting iFlow OAuth...").start();

    try {
      spinner.text = "Starting local server...";

      // Start local server for callback
      let callbackParams = null;
      const { port, close } = await startLocalServer((params) => {
        callbackParams = params;

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Verify the server is reachable (`curl ${server}/api/health`) and the URL config is correct.
  2. Re-login to the dashboard to refresh the Bearer token used by getServerCredentials().
  3. Ensure CLI and dashboard versions match so /api/cli/providers/iflow exists.
  4. Check server logs for the concrete rejection (validation, DB, auth).
  5. Retry — transient 5xx may pass on a second attempt.

Example fix

// before
if (!response.ok) {
  const error = await response.json();
  throw new Error(error.error || "Failed to save tokens");
}
// after
if (!response.ok) {
  const body = await response.text();
  let msg; try { msg = JSON.parse(body).error; } catch {}
  throw new Error(msg || `Failed to save tokens (HTTP ${response.status}): ${body.slice(0, 200)}`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate server credentials and required payload before the upload
const { server, token, userId } = getServerCredentials();
if (!server || !token) throw new Error("Missing server credentials — log in to the dashboard first");
if (!tokens.access_token || !userInfo.apiKey) throw new Error("Missing accessToken/apiKey required by the iflow provider endpoint");

Type guard

function hasServerError(data) {
  return data !== null && typeof data === "object" && typeof data.error === "string";
}

Try / catch

try {
  await iflowService.saveTokens(tokens, userInfo);
} catch (err) {
  if (/Failed to save tokens/.test(err.message) || err.message === "Failed to save tokens") {
    console.error("Server rejected the iflow connection — check server reachability, JWT freshness, and server logs.", err.message);
  } else { throw err; }
}

Prevention

When it happens

Trigger: saveTokens(tokens, userInfo) hits a server that returns 401 (expired Bearer token / wrong X-User-Id), 404 (route missing — CLI/server version mismatch), 400 (validation rejected e.g. missing apiKey), or 500 with a body lacking `error`.

Common situations: Stale JWT after server restart/JWT_SECRET rotation; wrong server URL in config; dashboard version older than the CLI expecting the iflow provider route; server DB failure returning an empty error body.

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/c34470662f6bd288. Report an issue: GitHub.