outline/outline · error · Error

Error while exchanging oauth code from Linear; status: ${res

Error message

Error while exchanging oauth code from Linear; status: ${res.status}

What it means

Thrown by Linear OAuth code exchange when POSTing the authorization code to Linear's token endpoint returns non-200. Uses env.LINEAR_CLIENT_ID and env.LINEAR_CLIENT_SECRET (non-null asserted with !). The error includes only the status, not the response body.

Source

Thrown at plugins/linear/server/linear.ts:59

      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
    };

    const body = new URLSearchParams();
    body.set("code", code);
    body.set("client_id", env.LINEAR_CLIENT_ID!);
    body.set("client_secret", env.LINEAR_CLIENT_SECRET!);
    body.set("redirect_uri", LinearUtils.callbackUrl());
    body.set("grant_type", "authorization_code");

    const res = await fetch(LinearUtils.tokenUrl, {
      method: "POST",
      headers,
      body,
    });

    if (res.status !== 200) {
      throw new Error(
        `Error while exchanging oauth code from Linear; status: ${res.status}`
      );
    }

    return AccessTokenResponseSchema.parse(await res.json());
  }

  static async refreshToken(refreshToken: string) {
    const headers = {
      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
    };

    const body = new URLSearchParams();
    body.set("refresh_token", refreshToken);
    body.set("client_id", env.LINEAR_CLIENT_ID!);
    body.set("client_secret", env.LINEAR_CLIENT_SECRET!);
    body.set("grant_type", "refresh_token");

View on GitHub (pinned to 935a44d4c0)

Solutions

  1. Confirm LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET are set and correct in server env
  2. Re-run the OAuth flow to obtain a fresh authorization code
  3. Verify LinearUtils.callbackUrl() matches the redirect URI in the Linear OAuth app
  4. Log the response body to get Linear's specific error reason

Example fix

// before
body.set('client_id', env.LINEAR_CLIENT_ID!);
if (res.status !== 200) {
  throw new Error(`Error while exchanging oauth code from Linear; status: ${res.status}`);
}

// after - fail fast on missing creds and include body
if (!env.LINEAR_CLIENT_ID || !env.LINEAR_CLIENT_SECRET) {
  throw new Error('Linear OAuth credentials are not configured');
}
body.set('client_id', env.LINEAR_CLIENT_ID);
if (res.status !== 200) {
  throw new Error(`Linear OAuth exchange failed (${res.status}): ${await res.text()}`);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!env.LINEAR_CLIENT_ID || !env.LINEAR_CLIENT_SECRET) {
  throw new Error('LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET must be configured');
}

Type guard

const hasLinearCreds = (): boolean =>
  Boolean(env.LINEAR_CLIENT_ID) && Boolean(env.LINEAR_CLIENT_SECRET);

Try / catch

try {
  await Linear.exchangeCode(code);
} catch (e) {
  if (e.message.startsWith('Error while exchanging oauth code from Linear')) notify('Linear connection failed - retry');
  else throw e;
}

Prevention

When it happens

Trigger: Expired/reused authorization code; redirect_uri mismatch; LINEAR_CLIENT_ID or LINEAR_CLIENT_SECRET unset or wrong; network blocking api.linear.app; Linear OAuth app revoked or suspended.

Common situations: LINEAR_CLIENT_ID/SECRET not configured in env (the ! assertion hides undefined at runtime); redirect URI changed; slow user on consent; integration app deleted from Linear.

Related errors


AI-assisted analysis of outline/outline@935a44d4c0 (2026-08-12). Data as JSON: /api/errors/8d739b71bf2543b2. Report an issue: GitHub.