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

  1. Treat it as cancellation, not a failure: catch it and return quietly without setting error state.
  2. Create a fresh AbortController per connect attempt instead of reusing a possibly-aborted one.
  3. Check controller.signal.aborted before invoking if prior cancellation is expected.
  4. 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

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


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