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

  1. This is intentional cancellation — restart loginOpenAICodexDevice() when you actually want to log in
  2. If unintended, check what is aborting the signal (your own timeout or UI code) and raise/adjust it
  3. Catch AIError.LoginCancelledError specifically so cancellation isn't logged as a failure
  4. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/6b80abf39197d351. Report an issue: GitHub.