mastra-ai/mastra · info

Login cancelled

Error message

Login cancelled

What it means

`pollDeviceCodeUntilComplete` implements the RFC 8628 device-authorization polling loop used by providers like Kimi Coding and xAI. Before every poll iteration it checks the caller-supplied AbortSignal; if the signal is already aborted it throws 'Login cancelled' instead of continuing to poll. This is an intentional cancellation exit, not a provider or network failure — the device flow was deliberately stopped by the caller (or an abortableSleep was interrupted mid-wait).

Source

Thrown at mastracode/sdk/src/auth/device-code.ts:173

/**
 * Blocking poll loop for TUI flows: waits the appropriate interval between
 * polls, honors slow_down growth, aborts on the signal, and throws on
 * failure/timeout (with a clock-drift hint after slow_down responses).
 */
export async function pollDeviceCodeUntilComplete<T>(options: {
  state: DeviceCodePollState;
  pollOnce: () => Promise<DeviceCodePollOutcome<T>>;
  signal?: AbortSignal;
  /** Override the sleep implementation for tests. */
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
}): Promise<T> {
  let state = options.state;
  const sleep = options.sleep ?? abortableSleep;

  while (true) {
    if (options.signal?.aborted) {
      throw new Error('Login cancelled');
    }
    if (Date.now() >= state.deadlineAt) {
      throw new Error(timeoutMessage(state));
    }

    await sleep(nextPollDelayMs(state), options.signal);

    const step = await stepDeviceCodePoll(state, options.pollOnce);
    state = step.state;

    if (step.status === 'complete') {
      return step.result;
    }
    if (step.status === 'failed') {
      throw new Error(step.error);
    }
  }
}

View on GitHub (pinned to 75dd419e61)

Solutions

  1. If the user cancelled, treat this as normal flow termination: clean up any pending device code and return to the login menu without retrying.
  2. If you did not intend to cancel, inspect the AbortController lifecycle — do not pass a signal from a controller you aborted earlier; create a fresh controller per login attempt.
  3. In server contexts, catch this error and map it to a 499/408-style response so the client's cancellation is not logged as a server error.
  4. To wait without cancellation risk, poll with stepDeviceCodePoll instead of the blocking loop and manage timeouts yourself.

Example fix

// before
const controller = new AbortController();
controller.abort(); // aborted earlier for an unrelated reason
const creds = await loginKimiCoding({ signal: controller.signal }); // throws 'Login cancelled'
// after
const controller = new AbortController();
try {
  const creds = await loginKimiCoding({ signal: controller.signal });
} catch (e) {
  if (e instanceof Error && e.message === 'Login cancelled') {
    return null; // user backed out — not an error
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (controller.signal.aborted) {
  // don't start the login at all — user already cancelled
  return null;
}

Type guard

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

Try / catch

try {
  const creds = await pollDeviceCodeUntilComplete({ state, pollOnce, signal });
} catch (e) {
  if (isLoginCancelled(e)) {
    return null; // deliberate cancellation — exit quietly, no retry
  }
  throw e; // real timeout/provider failure — propagate
}

Prevention

When it happens

Trigger: Calling pollDeviceCodeUntilComplete (directly or via loginKimiCoding/loginXAI) with a signal that is already aborted at loop entry, or aborting the signal while the loop is sleeping between polls (abortableSleep rejects with the same message).

Common situations: User presses Ctrl+C / Escape in a TUI login prompt while waiting to authorize in the browser; a web route times out or cancels its request while a pending device-code poll is in flight; a supervisor cancels a long-running login task; reusing a stale AbortController whose signal was aborted in an earlier attempt.

Related errors


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