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
- 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
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
- 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
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
- OpenRouter key exchange failed
- OpenRouter key exchange succeeded but no API key was…
- OpenRouter OAuth callback state did not match the request.
- OpenRouter OAuth returned an invalid callback URL.
- composio.authorize: entries must be strings
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)