{"record":{"id":"8d739b71bf2543b2","repo":"outline/outline","slug":"error-while-exchanging-oauth-code-from-linear-sta","errorCode":null,"errorMessage":"Error while exchanging oauth code from Linear; status: ${res.status}","messagePattern":"Error while exchanging oauth code from Linear; status: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/linear/server/linear.ts","lineNumber":59,"sourceCode":"      \"Content-Type\": \"application/x-www-form-urlencoded\",\n      Accept: \"application/json\",\n    };\n\n    const body = new URLSearchParams();\n    body.set(\"code\", code);\n    body.set(\"client_id\", env.LINEAR_CLIENT_ID!);\n    body.set(\"client_secret\", env.LINEAR_CLIENT_SECRET!);\n    body.set(\"redirect_uri\", LinearUtils.callbackUrl());\n    body.set(\"grant_type\", \"authorization_code\");\n\n    const res = await fetch(LinearUtils.tokenUrl, {\n      method: \"POST\",\n      headers,\n      body,\n    });\n\n    if (res.status !== 200) {\n      throw new Error(\n        `Error while exchanging oauth code from Linear; status: ${res.status}`\n      );\n    }\n\n    return AccessTokenResponseSchema.parse(await res.json());\n  }\n\n  static async refreshToken(refreshToken: string) {\n    const headers = {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n      Accept: \"application/json\",\n    };\n\n    const body = new URLSearchParams();\n    body.set(\"refresh_token\", refreshToken);\n    body.set(\"client_id\", env.LINEAR_CLIENT_ID!);\n    body.set(\"client_secret\", env.LINEAR_CLIENT_SECRET!);\n    body.set(\"grant_type\", \"refresh_token\");","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/outline/outline/blob/935a44d4c003429645a956141b2c4eb695f34b6e/plugins/linear/server/linear.ts#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Confirm LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET are set and correct in server env","Re-run the OAuth flow to obtain a fresh authorization code","Verify LinearUtils.callbackUrl() matches the redirect URI in the Linear OAuth app","Log the response body to get Linear's specific error reason"],"exampleFix":"// before\nbody.set('client_id', env.LINEAR_CLIENT_ID!);\nif (res.status !== 200) {\n  throw new Error(`Error while exchanging oauth code from Linear; status: ${res.status}`);\n}\n\n// after - fail fast on missing creds and include body\nif (!env.LINEAR_CLIENT_ID || !env.LINEAR_CLIENT_SECRET) {\n  throw new Error('Linear OAuth credentials are not configured');\n}\nbody.set('client_id', env.LINEAR_CLIENT_ID);\nif (res.status !== 200) {\n  throw new Error(`Linear OAuth exchange failed (${res.status}): ${await res.text()}`);\n}","handlingStrategy":"validation","validationCode":"if (!env.LINEAR_CLIENT_ID || !env.LINEAR_CLIENT_SECRET) {\n  throw new Error('LINEAR_CLIENT_ID and LINEAR_CLIENT_SECRET must be configured');\n}","typeGuard":"const hasLinearCreds = (): boolean =>\n  Boolean(env.LINEAR_CLIENT_ID) && Boolean(env.LINEAR_CLIENT_SECRET);","tryCatchPattern":"try {\n  await Linear.exchangeCode(code);\n} catch (e) {\n  if (e.message.startsWith('Error while exchanging oauth code from Linear')) notify('Linear connection failed - retry');\n  else throw e;\n}","preventionTips":["Fail fast if LINEAR_CLIENT_ID/SECRET are unset instead of relying on ! assertions","Match redirect_uri exactly with the Linear OAuth app","Use the authorization code immediately after consent"],"tags":["linear","oauth","integration","plugin"],"backgroundTag":null,"analyzedSha":"935a44d4c003429645a956141b2c4eb695f34b6e","analyzedAt":"2026-08-12T22:40:11.882Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}