{"record":{"id":"68fe915029d63735","repo":"coleam00/Archon","slug":"aborted","errorCode":"aborted","errorMessage":"Device flow polling was aborted","messagePattern":"Device flow polling was aborted","errorType":"error_code","errorClass":"DeviceFlowError","httpStatus":null,"severity":"info","filePath":"packages/core/src/github-auth/device-flow.ts","lineNumber":117,"sourceCode":"\nconst defaultSleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));\n\n/**\n * Step 2: poll until the user authorizes. Handles `authorization_pending`\n * (keep waiting) and `slow_down` (back off using the server-supplied interval).\n * Any other `error` is terminal and thrown as a DeviceFlowError.\n */\nexport async function pollDeviceFlow(\n  clientId: string,\n  deviceCode: string,\n  intervalSeconds: number,\n  opts: PollOptions = {}\n): Promise<DeviceAccessToken> {\n  const sleep = opts.sleep ?? defaultSleep;\n  let interval = Math.max(1, intervalSeconds);\n  for (;;) {\n    if (opts.signal?.aborted) {\n      throw new DeviceFlowError('aborted', 'Device flow polling was aborted');\n    }\n    await sleep(interval * 1000);\n    const result = await pollDeviceFlowOnce(clientId, deviceCode);\n    if (result.status === 'authorized') return result.token;\n    if (result.status === 'pending') continue;\n    if (result.status === 'slow_down') {\n      // Honor the server's new interval, else keep the current. Floor at 1s so a\n      // malformed `interval: 0` can't turn this into a busy loop.\n      interval = Math.max(1, result.interval);\n      continue;\n    }\n    throw new DeviceFlowError(result.code);\n  }\n}\n\n/** Result of a single (non-blocking) device-flow poll. */\nexport type PollOnceResult =\n  | { status: 'pending' }","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/core/src/github-auth/device-flow.ts#L99-L135","documentation":"pollDeviceFlow loops until the user authorizes the device. Before each sleep it checks opts.signal; if the caller aborted the poll, it throws DeviceFlowError with code 'aborted' so callers can distinguish intentional cancellation from auth failure.","triggerScenarios":"Calling pollDeviceFlow with an AbortSignal and aborting it (e.g. user cancels login, UI timeout, shutdown) while polling is still pending.","commonSituations":"User closes the browser before entering the code; CLI cancelled with Ctrl-C mapped to an AbortController; server shutting down mid-login; application-level login timeout.","solutions":["Treat code 'aborted' as an expected cancellation, not an auth failure — return to the login prompt","Only abort when you actually intend to cancel; pass signal: undefined otherwise","Wrap the poll in try/catch that special-cases aborted and restarts the device flow on demand"],"exampleFix":"// before\nconst token = await pollDeviceFlow(clientId, data, 5, { signal });\n// after\ntry {\n  const token = await pollDeviceFlow(clientId, data, 5, { signal });\n} catch (e) {\n  if (e instanceof DeviceFlowError && e.code === 'aborted') return null;\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { return await pollDeviceFlow(clientId, deviceCode, interval, { signal }); } catch (e) { if (e instanceof DeviceFlowError && e.code === 'aborted') return null; throw e; }","preventionTips":["Always special-case the 'aborted' code as normal control flow","Wire user-cancel and shutdown signals into the AbortController you pass, so abort is intentional"],"tags":["oauth","abort","cancellation","device-flow"],"backgroundTag":"operation-aborted","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}