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
- No fix needed if the cancellation was intentional — catch and treat as a normal exit.
- If the login should continue, do not abort the signal you pass in options (pass a fresh controller).
- For UIs, keep the AbortController alive for the full expected login duration (user must visit a browser, which can take minutes).
- 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
- Give the abort signal a generous timeout (minutes, not seconds).
- Abort only on genuine user cancel or unmount.
- Do not share a short-lived controller across long login flows.
- Treat this error as normal control flow, not a bug.
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
- Authentication for MCP server ${serverName} was cancelled.
- Login cancelled
- Failed to initiate OpenAI Codex device authorization: ${resp
- OpenAI Codex device authorization response missing required
- result.error (dynamic message from device-login poll failure
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/b619dd51811a58ee.
Report an issue: GitHub.