tinyhumansai/openhuman · warning · Error
OpenRouter OAuth was cancelled.
Error message
OpenRouter OAuth was cancelled.
What it means
connectOpenRouterViaOAuth accepts an optional AbortSignal via deps.signal. Right after the loopback listener starts, the function checks signal?.aborted; if the caller already cancelled (dialog closed, component unmounted, timeout), it cancels the listener and throws this cooperative-cancellation error before the browser window is ever opened.
Source
Thrown at app/src/utils/openrouterOAuth.ts:131
// sibling OAuthProviderButton flow, which trusts the bound port).
parsed.hostname = 'localhost';
return parsed.toString();
}
export async function connectOpenRouterViaOAuth(deps: OpenRouterOAuthDeps = {}): Promise<string> {
const startLoopbackListener = deps.startLoopbackListener ?? startLoopbackOauthListener;
const openExternalUrl = deps.openExternalUrl ?? openUrl;
const fetchImpl = deps.fetchImpl ?? fetch;
const signal = deps.signal;
const loopback = await startLoopbackListener({ port: OPENROUTER_LOOPBACK_PORT });
if (!loopback) {
throw new Error('OpenRouter OAuth requires the desktop app. Use an API key instead.');
}
if (signal?.aborted) {
await loopback.cancel();
throw new Error('OpenRouter OAuth was cancelled.');
}
const verifier = randomVerifier();
const challenge = await createCodeChallenge(verifier);
const authUrl = new URL(OPENROUTER_AUTH_URL);
authUrl.searchParams.set('callback_url', toOpenRouterCallbackUrl(loopback.redirectUri));
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', PKCE_METHOD);
try {
await openExternalUrl(authUrl.toString());
const callbackUrl = await Promise.race([
loopback.awaitCallback(),
new Promise<string>((_, reject) => {
if (!signal) return;
const onAbort = () => {
signal.removeEventListener('abort', onAbort);
reject(new Error('OpenRouter OAuth was cancelled.'));View on GitHub (pinned to a221052e0d)
Solutions
- Treat it as cancellation, not a failure: catch it and return quietly without setting error state.
- Create a fresh AbortController per connect attempt instead of reusing a possibly-aborted one.
- Check controller.signal.aborted before invoking if prior cancellation is expected.
- If it fires unexpectedly, audit every .abort() call site (cleanup functions, timeout wrappers, dialog close handlers).
Example fix
// before
try { await connectOpenRouterViaOAuth({ signal }); } catch (e) { setError(e); }
// after
try { await connectOpenRouterViaOAuth({ signal }); } catch (e) {
if (e instanceof Error && e.message === 'OpenRouter OAuth was cancelled.') return; // user cancel, not an error
setError(e);
} Defensive patterns
Strategy: try-catch
Validate before calling
if (controller.signal.aborted) {
return; // skip the call entirely — nothing to cancel
} Try / catch
try {
await connectOpenRouterViaOAuth({ signal: controller.signal });
} catch (err) {
if (err instanceof Error && err.message === 'OpenRouter OAuth was cancelled.') {
return; // cooperative cancel — no error UI
}
throw err;
} Prevention
- Abort only on real cancel intents (unmount, explicit Cancel) and swallow the matching cancellation error.
- Use one fresh AbortController per connect attempt; never reuse an aborted controller.
- Render cancellation as info (dialog closed), not as an error toast.
When it happens
Trigger: Passing deps.signal from an AbortController that aborted during listener startup: React effect cleanup ran, the settings modal closed, a race between a Cancel button and Connect, or React 18 StrictMode double-mount aborting the first attempt.
Common situations: User clicks Connect then immediately closes the dialog; StrictMode mounts/unmounts the connecting component in dev; a wrapper enforces a timeout via AbortSignal.timeout() that expires quickly.
Related errors
- OpenRouter OAuth returned an invalid callback URL.
- OpenRouter OAuth callback state did not match the request.
- OpenRouter OAuth did not return an authorization code.
- OpenRouter key exchange failed (${response.status}).
- OpenRouter key exchange succeeded but no API key was returne
AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16).
Data as JSON: /api/errors/4119e6a278f0d36a.
Report an issue: GitHub.