can1357/oh-my-pi · info · AIError.LoginCancelledError
Device authorization cancelled
Error message
Device authorization cancelled
What it means
This is an AIError.LoginCancelledError (not OAuthError) thrown inside the device-token polling loop of loginOpenAICodexDevice when the caller-supplied AbortSignal is observed to be aborted between polls. It lets a user or host application stop a pending 'waiting for browser authorization' flow cleanly instead of polling to exhaustion.
Source
Thrown at packages/ai/src/registry/oauth/openai-codex.ts:294
const pollIntervalMs =
(typeof initData.interval === "number"
? initData.interval
: parseInt(String(initData.interval ?? "5"), 10) || 5) *
1000 +
DEVICE_POLL_SAFETY_MARGIN_MS;
ctrl.onAuth?.({
url: DEVICE_AUTH_URL,
instructions: `Enter code: ${userCode}`,
});
ctrl.onProgress?.(`Waiting for browser authorization (code: ${userCode})…`);
for (let poll = 0; poll < DEVICE_MAX_POLLS; poll++) {
await Bun.sleep(poll === 0 ? Math.min(pollIntervalMs, DEVICE_POLL_INTERVAL_MS) : pollIntervalMs);
if (ctrl.signal?.aborted) {
throw new AIError.LoginCancelledError("Device authorization cancelled");
}
const pollResponse = await fetch(DEVICE_TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
device_auth_id: initData.device_auth_id,
user_code: userCode,
}),
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
});
// 403/404 = authorization pending, keep polling
if (pollResponse.status === 403 || pollResponse.status === 404) {
continue;
}
if (!pollResponse.ok) {View on GitHub (pinned to 9690622007)
Solutions
- This is intentional cancellation — restart loginOpenAICodexDevice() when you actually want to log in
- If unintended, check what is aborting the signal (your own timeout or UI code) and raise/adjust it
- Catch AIError.LoginCancelledError specifically so cancellation isn't logged as a failure
- Make sure the same AbortController isn't shared with other operations that finish earlier and abort early
Example fix
// before: cancellation treated as generic failure
try { await loginOpenAICodexDevice(); } catch (e) { log.error(e); }
// after: distinguish user cancellation
try {
await loginOpenAICodexDevice();
} catch (e) {
if (e instanceof AIError.LoginCancelledError) { log.info('Login cancelled by user'); return; }
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// pass your own signal only if you intend cancellation; pre-check its state
if (ctrl.signal?.aborted) {
// don't even start the device flow — it will be cancelled immediately
} Try / catch
try {
await loginOpenAICodexDevice();
} catch (e) {
if (e instanceof AIError.LoginCancelledError) {
// expected user cancellation — log at info level and exit cleanly
return;
}
throw e;
} Prevention
- Use a dedicated AbortController for the login so unrelated aborts don't cancel it
- Catch AIError.LoginCancelledError separately from OAuthError in UI code
- Only wire the signal when you actually provide a cancel affordance to the user
- Set external timeouts longer than DEVICE_MAX_POLLS * pollIntervalMs if you rely on them instead of the built-in budget
When it happens
Trigger: The host passes ctrl.signal and aborts it (user pressing Ctrl-C in an interactive prompt, UI cancel button, or an outer timeout aborting the controller) while the loop is sleeping between DEVICE_TOKEN_URL polls.
Common situations: User cancels the login dialog in a TUI; an orchestrator enforces its own overall timeout by aborting; test harnesses aborting after a short delay; Ctrl-C during CLI login being translated into an abort.
Related errors
- Device authorization initiation failed: ${initResponse.statu
- Device authorization response missing required fields
- Too many pending authorization requests. Please try again la
- Failed to initiate device authorization: ${initiateResponse.
- Kilo device authorization response missing required fields
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6b80abf39197d351.
Report an issue: GitHub.