can1357/oh-my-pi · info · AIError.LoginCancelledError
Login cancelled
Error message
Login cancelled
What it means
During Kilo's device-flow polling loop, loginKilo checks callbacks.signal?.aborted before each poll and throws LoginCancelledError when the caller's AbortSignal has fired. This is the library's normal way to surface a user-initiated or programmatic cancellation of the pending device login — it is not a provider failure.
Source
Thrown at packages/ai/src/registry/kilo.ts:61
const userCode = initiateData.code;
const verificationUrl = initiateData.verificationUrl;
const expiresInSeconds = initiateData.expiresIn;
if (!userCode || !verificationUrl || typeof expiresInSeconds !== "number" || expiresInSeconds <= 0) {
throw new AIError.OAuthError("Kilo device authorization response missing required fields", {
kind: "validation",
provider: "kilo",
});
}
callbacks.onAuth?.({
url: verificationUrl,
instructions: `Enter code: ${userCode}`,
});
const deadline = Date.now() + expiresInSeconds * 1000;
while (Date.now() < deadline) {
if (callbacks.signal?.aborted) {
throw new AIError.LoginCancelledError();
}
const pollResponse = await fetchImpl(`${KILO_DEVICE_AUTH_BASE_URL}/codes/${encodeURIComponent(userCode)}`);
if (pollResponse.status === 202) {
await Bun.sleep(POLL_INTERVAL_MS);
continue;
}
if (pollResponse.status === 403) {
throw new AIError.OAuthError("Authorization was denied", { kind: "device-auth", provider: "kilo" });
}
if (pollResponse.status === 410) {
throw new AIError.OAuthError("Authorization code expired. Please try again.", {
kind: "device-auth",
provider: "kilo",
});
}
if (!pollResponse.ok) {
throw new AIError.OAuthError(`Failed to poll device authorization: ${pollResponse.status}`, {View on GitHub (pinned to 9690622007)
Solutions
- If cancellation was intentional, catch LoginCancelledError and treat login as cleanly aborted — no retry needed.
- If it was accidental, re-run login without aborting the signal and complete the code entry at the verification URL.
- Ensure the AbortController isn't shared with unrelated operations that abort earlier.
- Check UI wiring so the abort only fires on genuine cancel actions.
Example fix
// before
await loginKilo(callbacks); // unhandled LoginCancelledError on Ctrl+C
// after
try {
await loginKilo(callbacks);
} catch (err) {
if (err instanceof AIError.LoginCancelledError) return; // user cancelled
throw err;
} Defensive patterns
Strategy: try-catch
Type guard
function isLoginCancelled(err: unknown): err is AIError.LoginCancelledError {
return err instanceof AIError.LoginCancelledError || (err instanceof Error && err.name === "LoginCancelledError");
} Try / catch
try {
await loginKilo(callbacks);
} catch (err) {
if (isLoginCancelled(err)) return null; // clean cancel, not an error path
throw err;
} Prevention
- Wire the AbortSignal only to genuine user-cancel actions.
- Don't share one AbortController across unrelated operations.
- Treat LoginCancelledError as control flow, not failure — no retry.
- Give users a visible way to resume/restart a cancelled login.
When it happens
Trigger: The AbortSignal passed via callbacks.signal is aborted (e.g. user pressed Ctrl+C / Esc in the UI, or code called AbortController.abort()) while loginKilo is between polls waiting for authorization within the expiresIn deadline.
Common situations: User cancels the login prompt in the TUI; an upstream timeout aborts the signal; application code aborts pending OAuth when a screen closes or a different login method is chosen.
Related errors
- OAuth refresh ownership aborted by caller
- Too many pending authorization requests. Please try again la
- Failed to initiate device authorization: ${initiateResponse.
- Kilo device authorization response missing required fields
- Authorization was denied
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/17732848328a5bba.
Report an issue: GitHub.