{"record":{"id":"76bc0f8493623709","repo":"tinyhumansai/openhuman","slug":"openrouter-oauth-did-not-return-an-authorization-c","errorCode":null,"errorMessage":"OpenRouter OAuth did not return an authorization code.","messagePattern":"OpenRouter OAuth did not return an authorization code\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/utils/openrouterOAuth.ts","lineNumber":60,"sourceCode":"  return base64UrlEncode(new Uint8Array(digest));\n}\n\nfunction extractOAuthCode(callbackUrl: string, expectedState: string): string {\n  let parsed: URL;\n  try {\n    parsed = new URL(callbackUrl);\n  } catch {\n    throw new Error('OpenRouter OAuth returned an invalid callback URL.');\n  }\n\n  const actualState = parsed.searchParams.get('state');\n  if (actualState !== expectedState) {\n    throw new Error('OpenRouter OAuth callback state did not match the request.');\n  }\n\n  const code = parsed.searchParams.get('code');\n  if (!code) {\n    throw new Error('OpenRouter OAuth did not return an authorization code.');\n  }\n  return code;\n}\n\nasync function exchangeCodeForKey(\n  code: string,\n  verifier: string,\n  fetchImpl: typeof fetch\n): Promise<string> {\n  const response = await fetchImpl(OPENROUTER_TOKEN_URL, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: PKCE_METHOD }),\n  });\n\n  let body: OpenRouterExchangeResponse | null = null;\n  try {\n    body = (await response.json()) as OpenRouterExchangeResponse;","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/app/src/utils/openrouterOAuth.ts#L42-L78","documentation":"The callback URL parsed and the state matched, but searchParams has no `code` entry — the authorization grant never arrived. In OAuth this is the shape of a denied or failed authorization: providers redirect with error=access_dened (or similar) and omit code instead of returning an error status.","triggerScenarios":"User clicked 'Deny'/cancelled on OpenRouter's consent page; provider redirected with ?error=... instead of ?code=...; callback captured before the provider appended parameters (premature capture from a partial deep-link event).","commonSituations":"User backs out of consent; OpenRouter session expired mid-flow and it redirected to login with error params; deep-link event fired twice and the second delivery carries no query.","solutions":["Inspect parsed.searchParams.get('error') / 'error_description' in the same handler — it usually names the denial reason","Treat as user cancellation in UI (return to settings, no error toast) when error=access_denied","Restart the flow cleanly (new verifier/state) — there is no code to recover"],"exampleFix":"// before\nconst code = extractOAuthCode(callbackUrl, state);\n// after\nconst errParam = new URL(callbackUrl).searchParams.get('error');\nif (errParam) { cancelFlow(errParam); } else { const code = extractOAuthCode(callbackUrl, state); }","handlingStrategy":"validation","validationCode":"const params = new URL(callbackUrl).searchParams;\nif (!params.get('code')) {\n  const oauthError = params.get('error');\n  handleDenial(oauthError ?? 'no code returned');\n  return;\n}","typeGuard":"function callbackHasCode(callbackUrl: string): boolean {\n  try { return new URL(callbackUrl).searchParams.has('code'); } catch { return false; }\n}","tryCatchPattern":"try { const code = extractOAuthCode(callbackUrl, state); }\ncatch (err) {\n  if (err instanceof Error && err.message.includes('did not return an authorization code')) {\n    // user denied or provider errored — end flow gracefully, offer restart\n  } else throw err;\n}","preventionTips":["Read ?error / ?error_description alongside code — providers encode denial there","Design UI for cancellation as a normal path, not an exception","Guard against duplicate deep-link events delivering a stripped second callback"],"tags":["oauth","openrouter","authorization-denied","callback"],"backgroundTag":"oauth-authorization-denied","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}