{"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":"Error","httpStatus":null,"severity":"warning","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/a221052e0df5b1f7598fceba7329fd1af95d6699/app/src/utils/openrouterOAuth.ts#L42-L78","documentation":"The callback URL parsed and state matched, but there is no `code` query parameter — the authorization server redirected back without granting. Almost always the user denied consent, or the server appended error=access_denied/invalid_request instead of a code.","triggerScenarios":"User clicks Deny on OpenRouter's consent screen; OpenRouter redirects with ?error=access_denied; a malformed client_id or scope produces an error redirect instead of a code.","commonSituations":"User denies the requested scopes; upstream app credentials misconfigured; scope string typo; user navigates back mid-flow.","solutions":["Read parsed.searchParams.get('error') and surface it — it explains the missing code","Treat access_denied as user cancellation (info-level UX), not an exception dialog","If error is invalid_request, fix client_id/scope configuration before retrying"],"exampleFix":"// before\nconst code = parsed.searchParams.get('code');\nif (!code) throw new Error('OpenRouter OAuth did not return an authorization code.');\n\n// after — name the actual reason\nconst oauthErr = parsed.searchParams.get('error');\nif (oauthErr) {\n  throw new Error(`OpenRouter OAuth failed: ${oauthErr}`);\n}","handlingStrategy":"validation","validationCode":"const params = new URL(callbackUrl).searchParams;\nconst failedOrDenied = params.has('error') || !params.has('code');\nif (failedOrDenied) {\n  // surface as cancellation with params.get('error') detail; do not call extractOAuthCode\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Check the error query parameter before treating a missing code as exceptional","Present access_denied as 'cancelled' in the UI, not an error dialog","Validate client_id and scopes before opening the authorize URL"],"tags":["oauth","openrouter","authorization-code","cancel"],"backgroundTag":null,"analyzedSha":"a221052e0df5b1f7598fceba7329fd1af95d6699","analyzedAt":"2026-08-16T12:47:06.542Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}