{"record":{"id":"71572829245c0089","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":"skill/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/f88b2837a7d7c3182e46307bbbb091a1ed547571/skill/scripts/live-poll.mjs#L175-L211","documentation":"Inside fetchNextEvent() (skill/scripts/live-poll.mjs), each long-poll round issues GET ${base}/poll?token=...&timeout=...&types=...&lease=... . A 401 is first converted to AUTH_FAILED; any other non-OK response throws 'Poll failed: <status> <statusText>', aborting the poll loop. Note the protocol itself returns HTTP 200 with a {type:'timeout'} JSON body for normal idle expiry — so this error always means an abnormal HTTP-level failure, never a routine poll timeout.","triggerScenarios":"The live server returns 500 from /poll (server-side exception); a reverse proxy in front times out the intentionally idle long-poll connection and answers 504/502; the server restarted mid-poll and the next request lands on a replacement process that errors; base/port drift so a foreign service answers.","commonSituations":"nginx/Caddy or dev proxies with read timeouts shorter than the poll slice (perRequestTimeoutMs) killing idle upgrades; server crashes during heavy operations; mixed old client against newer server where query parameters are rejected.","solutions":["Check the live-server console — 5xx statuses almost always have a matching stack trace there","If a proxy fronts the server, raise its proxy_read_timeout above the poll slice so idle long polls are not cut into 504s","Verify base and token still match the running server (a mismatch shows up as 401/AUTH_FAILED, which is handled separately)","Retry fetchNextEvent with backoff — a transient 5xx during a server reload or while it is busy scaffolding is expected to clear"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight the poll endpoint once before entering the long-poll loop:\nconst probe = await fetch(`${base}/poll?token=${token}&timeout=1000`);\nif (probe.status === 401) throw new Error('token mismatch — re-read server token');\nif (!probe.ok) throw new Error(`poll endpoint unhealthy: HTTP ${probe.status}`);","typeGuard":null,"tryCatchPattern":"while (running) {\n  try {\n    event = await fetchNextEvent(base, token, { signal });\n  } catch (err) {\n    if (err?.code === 'AUTH_FAILED') throw err;            // stop: fix the token\n    if (!/^Poll failed: 5\\d\\d/.test(err?.message || '')) throw err;\n    await backoff();                                        // transient 5xx: retry the loop\n    continue;\n  }\n}","preventionTips":["Set proxy read timeouts above the poll slice when a proxy fronts the live server — idle long polls are by design, not hangs","Watch the live-server console for 5xx root causes instead of retrying blindly","Treat 401/AUTH_FAILED as a distinct terminal case: the server token changed under you"],"tags":["http","long-poll","live-server","network"],"backgroundTag":"http-error-response","analyzedSha":"f88b2837a7d7c3182e46307bbbb091a1ed547571","analyzedAt":"2026-08-18T04:58:36.608Z","contentChangedAt":"2026-08-18T04:58:36.608Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}