{"record":{"id":"17732848328a5bba","repo":"can1357/oh-my-pi","slug":"login-cancelled-177328","errorCode":null,"errorMessage":"Login cancelled","messagePattern":"Login cancelled","errorType":"exception","errorClass":"AIError.LoginCancelledError","httpStatus":null,"severity":"info","filePath":"packages/ai/src/registry/kilo.ts","lineNumber":61,"sourceCode":"\tconst userCode = initiateData.code;\n\tconst verificationUrl = initiateData.verificationUrl;\n\tconst expiresInSeconds = initiateData.expiresIn;\n\tif (!userCode || !verificationUrl || typeof expiresInSeconds !== \"number\" || expiresInSeconds <= 0) {\n\t\tthrow new AIError.OAuthError(\"Kilo device authorization response missing required fields\", {\n\t\t\tkind: \"validation\",\n\t\t\tprovider: \"kilo\",\n\t\t});\n\t}\n\n\tcallbacks.onAuth?.({\n\t\turl: verificationUrl,\n\t\tinstructions: `Enter code: ${userCode}`,\n\t});\n\n\tconst deadline = Date.now() + expiresInSeconds * 1000;\n\twhile (Date.now() < deadline) {\n\t\tif (callbacks.signal?.aborted) {\n\t\t\tthrow new AIError.LoginCancelledError();\n\t\t}\n\n\t\tconst pollResponse = await fetchImpl(`${KILO_DEVICE_AUTH_BASE_URL}/codes/${encodeURIComponent(userCode)}`);\n\t\tif (pollResponse.status === 202) {\n\t\t\tawait Bun.sleep(POLL_INTERVAL_MS);\n\t\t\tcontinue;\n\t\t}\n\t\tif (pollResponse.status === 403) {\n\t\t\tthrow new AIError.OAuthError(\"Authorization was denied\", { kind: \"device-auth\", provider: \"kilo\" });\n\t\t}\n\t\tif (pollResponse.status === 410) {\n\t\t\tthrow new AIError.OAuthError(\"Authorization code expired. Please try again.\", {\n\t\t\t\tkind: \"device-auth\",\n\t\t\t\tprovider: \"kilo\",\n\t\t\t});\n\t\t}\n\t\tif (!pollResponse.ok) {\n\t\t\tthrow new AIError.OAuthError(`Failed to poll device authorization: ${pollResponse.status}`, {","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/registry/kilo.ts#L43-L79","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait loginKilo(callbacks); // unhandled LoginCancelledError on Ctrl+C\n// after\ntry {\n  await loginKilo(callbacks);\n} catch (err) {\n  if (err instanceof AIError.LoginCancelledError) return; // user cancelled\n  throw err;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isLoginCancelled(err: unknown): err is AIError.LoginCancelledError {\n  return err instanceof AIError.LoginCancelledError || (err instanceof Error && err.name === \"LoginCancelledError\");\n}","tryCatchPattern":"try {\n  await loginKilo(callbacks);\n} catch (err) {\n  if (isLoginCancelled(err)) return null; // clean cancel, not an error path\n  throw err;\n}","preventionTips":["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."],"tags":["oauth","kilo","device-flow","abort","cancellation"],"backgroundTag":"operation-aborted","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}