tinyhumansai/openhuman · error

OpenRouter OAuth did not return an authorization code.

Error message

OpenRouter OAuth did not return an authorization code.

What it means

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.

Solutions

  1. Inspect parsed.searchParams.get('error') / 'error_description' in the same handler — it usually names the denial reason
  2. Treat as user cancellation in UI (return to settings, no error toast) when error=access_denied
  3. Restart the flow cleanly (new verifier/state) — there is no code to recover

Example fix

// before
const code = extractOAuthCode(callbackUrl, state);
// after
const errParam = new URL(callbackUrl).searchParams.get('error');
if (errParam) { cancelFlow(errParam); } else { const code = extractOAuthCode(callbackUrl, state); }
Defensive patterns

Strategy: validation

Validate before calling

const params = new URL(callbackUrl).searchParams;
if (!params.get('code')) {
  const oauthError = params.get('error');
  handleDenial(oauthError ?? 'no code returned');
  return;
}

Type guard

function callbackHasCode(callbackUrl: string): boolean {
  try { return new URL(callbackUrl).searchParams.has('code'); } catch { return false; }
}

Try / catch

try { const code = extractOAuthCode(callbackUrl, state); }
catch (err) {
  if (err instanceof Error && err.message.includes('did not return an authorization code')) {
    // user denied or provider errored — end flow gracefully, offer restart
  } else throw err;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/76bc0f8493623709. Report an issue: GitHub.

Appendix: source

Thrown at app/src/utils/openrouterOAuth.ts:60

  return base64UrlEncode(new Uint8Array(digest));
}

function extractOAuthCode(callbackUrl: string, expectedState: string): string {
  let parsed: URL;
  try {
    parsed = new URL(callbackUrl);
  } catch {
    throw new Error('OpenRouter OAuth returned an invalid callback URL.');
  }

  const actualState = parsed.searchParams.get('state');
  if (actualState !== expectedState) {
    throw new Error('OpenRouter OAuth callback state did not match the request.');
  }

  const code = parsed.searchParams.get('code');
  if (!code) {
    throw new Error('OpenRouter OAuth did not return an authorization code.');
  }
  return code;
}

async function exchangeCodeForKey(
  code: string,
  verifier: string,
  fetchImpl: typeof fetch
): Promise<string> {
  const response = await fetchImpl(OPENROUTER_TOKEN_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, code_verifier: verifier, code_challenge_method: PKCE_METHOD }),
  });

  let body: OpenRouterExchangeResponse | null = null;
  try {
    body = (await response.json()) as OpenRouterExchangeResponse;

View on GitHub (pinned to 7491200858)