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
- If the user cancelled, treat this as normal flow termination: clean up any pending device code and return to the login menu without retrying.
- 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.
- 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.
- 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
- Check signal.aborted before initiating the login loop to fail fast.
- Use one AbortController per login attempt; never reuse aborted controllers.
- Catch 'Login cancelled' separately from timeouts so cancellation is not logged as an error.
- For non-blocking flows, prefer stepDeviceCodePoll so cancellation is handled by your own request lifecycle.
- In UIs, disable the cancel path only where cancellation would orphan server-side state.
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
- ${response.status} ${response.statusText}: ${text}
- Invalid device code response
- Invalid device code response fields
- Kimi For Coding device authorization failed: ${response.stat
- Invalid Kimi For Coding device authorization response
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/65ebb9875508ab4e.
Report an issue: GitHub.