{"record":{"id":"efd97c4f9f5be968","repo":"pbakaus/impeccable","slug":"poll-failed-res-status-res-statustext","errorCode":null,"errorMessage":"Poll failed: ${res.status} ${res.statusText}","messagePattern":"Poll failed: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugin/skills/impeccable/scripts/live-poll.mjs","lineNumber":193,"sourceCode":"      : PER_REQUEST_TIMEOUT_MS;\n    const slice = Math.min(Math.max(remaining, 1000), perRequestTimeoutMs);\n    const query = new URLSearchParams({\n      token,\n      timeout: String(slice),\n      leaseMs: String(leaseMs),\n    });\n    const normalizedTypes = normalizePollTypes(resolveTypes ? await resolveTypes() : types);\n    if (normalizedTypes.length > 0) query.set('types', normalizedTypes.join(','));\n    const res = await fetch(`${base}/poll?${query}`, { signal });\n\n    if (res.status === 401) {\n      const err = new Error('Authentication failed. The server token may have changed.');\n      err.code = 'AUTH_FAILED';\n      throw err;\n    }\n\n    if (!res.ok) {\n      throw new Error(`Poll failed: ${res.status} ${res.statusText}`);\n    }\n\n    const next = await res.json();\n    if (next?.type === 'timeout') {\n      if (totalDeadline && Date.now() < totalDeadline) continue;\n      if (!totalDeadline) continue;\n      return next;\n    }\n    return next;\n  }\n}\n\nexport async function augmentEventWithAcceptHandling(event, base, token) {\n  if (event.type !== 'accept' && event.type !== 'discard') return event;\n\n  const __dirname = path.dirname(fileURLToPath(import.meta.url));\n  const acceptScript = path.join(__dirname, 'live-accept.mjs');\n  const scriptArgs = buildAcceptScriptArgs(event);","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/plugin/skills/impeccable/scripts/live-poll.mjs#L175-L211","documentation":"Thrown by fetchNextEvent() when the GET /poll long-poll request returns a non-ok, non-401 status. Distinct from error 55 because /poll is the long-poll endpoint (with timeout/leaseMs/types query params). Like /status, 401 maps to AUTH_FAILED; any other status becomes 'Poll failed: <code> <reason>'. The function loops on 'timeout' responses, so this throw breaks the loop.","triggerScenarios":"Server returns 500 due to an internal error mid-lease; 404 if the /poll route is missing (version skew); 429 if the server rate-limits; 503 during shutdown. The poll uses a perRequestTimeoutMs capped at 270s (PER_REQUEST_TIMEOUT_MS) to stay under Node fetch's 300s header timeout.","commonSituations":"Server crashed under load; polling after the server was stopped but before the port was released to another process; version mismatch where the client sends query params the server doesn't understand; lease corruption causing the server to error on event delivery.","solutions":["Restart the live server (stop via server.json pid, then `live.mjs`) and resume polling.","Check server logs for the 5xx root cause — poll errors usually mirror an internal exception.","Verify client and server are from the same skill version (the /poll contract includes token, timeout, leaseMs, types).","If ECONNREFUSED, the server isn't running — that's surfaced separately by handlePollError, but a stale server.json can mislead."],"exampleFix":"// before\nconst event = await fetchNextEvent(base, token, { types });\n\n// after\ntry {\n  const event = await fetchNextEvent(base, token, { types });\n} catch (err) {\n  if (err.code === 'AUTH_FAILED') { /* re-auth */ }\n  else if (/Poll failed:/.test(err.message)) {\n    console.error('Poll endpoint error, restart live session:', err.message);\n  }\n  throw err;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function safePoll(base, token, opts, retries = 1) {\n  for (let attempt = 0; ; attempt++) {\n    try {\n      return await fetchNextEvent(base, token, opts);\n    } catch (err) {\n      if (err.code === 'AUTH_FAILED') throw err;\n      if (attempt < retries && /Poll failed: 5\\d\\d/.test(err.message)) {\n        await new Promise((r) => setTimeout(r, 500));\n        continue;\n      }\n      throw err;\n    }\n  }\n}","preventionTips":["Distinguish AUTH_FAILED (restart auth) from transient 5xx (retry) from ECONNREFUSED (server down — restart live.mjs).","Keep client and server on the same skill build so /poll query params (token, timeout, leaseMs, types) match.","fetchNextEvent loops internally on 'timeout' responses; only non-ok throws break the loop, so surface them."],"tags":["live-poll","network","http","long-poll"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}