tinyhumansai/openhuman · warning · 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 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.

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 a221052e0d)

Solutions

  1. Read parsed.searchParams.get('error') and surface it — it explains the missing code
  2. Treat access_denied as user cancellation (info-level UX), not an exception dialog
  3. If error is invalid_request, fix client_id/scope configuration before retrying

Example fix

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

// after — name the actual reason
const oauthErr = parsed.searchParams.get('error');
if (oauthErr) {
  throw new Error(`OpenRouter OAuth failed: ${oauthErr}`);
}
Defensive patterns

Strategy: validation

Validate before calling

const params = new URL(callbackUrl).searchParams;
const failedOrDenied = params.has('error') || !params.has('code');
if (failedOrDenied) {
  // surface as cancellation with params.get('error') detail; do not call extractOAuthCode
}

Prevention

When it happens

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

Common situations: User denies the requested scopes; upstream app credentials misconfigured; scope string typo; user navigates back mid-flow.

Related errors


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