mastra-ai/mastra · info

Login cancelled

Error message

Login cancelled

What it means

The AbortSignal passed to loginOpenAICodexDevice was aborted while the library was waiting between device-login poll attempts. The library checks options.signal.aborted at the top of each poll iteration and throws to let the caller cancel a pending login. This is intentional cancellation, not a fault in the flow.

Source

Thrown at mastracode/sdk/src/auth/providers/openai-codex.ts:548

async function loginOpenAICodexDevice(options: {
  onAuth: (info: { url: string; instructions?: string }) => void;
  onProgress?: (message: string) => void;
  signal?: AbortSignal;
  sleep?: (ms: number) => Promise<void>;
}): Promise<OAuthCredentials> {
  const pending = await startCodexDeviceLogin({ signal: options.signal });
  const sleep = options.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)));

  options.onAuth({
    url: pending.url,
    instructions: pending.instructions,
  });

  await sleep(pending.intervalMs);

  while (true) {
    if (options.signal?.aborted) {
      throw new Error('Login cancelled');
    }

    const result = await pollCodexDeviceLogin(pending, { signal: options.signal });
    if (result.status === 'complete') {
      return result.credentials;
    }
    if (result.status === 'failed') {
      throw new Error(result.error);
    }

    options.onProgress?.('Waiting for OpenAI Codex device authorization...');
    await sleep(result.nextPollMs);
  }
}

/**
 * Login with OpenAI Codex OAuth
 *

View on GitHub (pinned to 75dd419e61)

Solutions

  1. No fix needed if the cancellation was intentional — catch and treat as a normal exit.
  2. If the login should continue, do not abort the signal you pass in options (pass a fresh controller).
  3. For UIs, keep the AbortController alive for the full expected login duration (user must visit a browser, which can take minutes).
  4. On catch, show the user_code/verification URL again and offer to restart the login rather than silently exiting.

Example fix

// before: controller aborted too early
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // 5s is too short
await loginOpenAICodexDevice({ signal: controller.signal });

// after: allow enough time for browser login
const controller = new AbortController();
setTimeout(() => controller.abort(), 10 * 60 * 1000); // 10 minutes
try {
  await loginOpenAICodexDevice({ signal: controller.signal });
} catch (e) {
  if (e.message === 'Login cancelled') return; // treat as user cancel
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

function isAborted(e: unknown): boolean {
  return e instanceof Error && e.message === 'Login cancelled';
}

Type guard

const isLoginCancelled = (e: unknown): e is Error =>
  e instanceof Error && e.message === 'Login cancelled';

Try / catch

try {
  await loginOpenAICodexDevice({ signal: controller.signal });
} catch (e) {
  if (isLoginCancelled(e)) {
    console.log('Login cancelled by user.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: The caller's AbortController is aborted (e.g. user pressed Ctrl+C, UI timeout, component unmounted) while loginOpenAICodexDevice was sleeping between polls of the device-authorization endpoint.

Common situations: CLI login cancelled by the user; React component unmount aborting the signal; a parent operation timing out and aborting all child signals; CI job timeout aborting an interactive login.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/b619dd51811a58ee. Report an issue: GitHub.