{"record":{"id":"71572829245c0089","repo":"pbakaus/impeccable","slug":"poll-failed-res-status-res-statustext-715728","errorCode":null,"errorMessage":"Poll failed: ${res.status} ${res.statusText}","messagePattern":"Poll failed: (.+?) (.+?)","errorType":"exception","errorClass":null,"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/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/skill/scripts/live-poll.mjs#L175-L211","documentation":"Thrown by fetchNextEvent() in live-poll.mjs when the long-poll GET {base}/poll?... returns a non-OK status other than 401. The 401 path throws AUTH_FAILED; a 'timeout' type response continues the loop; any other error status collapses to this message with the numeric code and statusText. This is the main event-stream entry point, so this error breaks the agent's event loop.","triggerScenarios":"fetchNextEvent(base, token, {types, totalDeadline, signal}) is called in the live agent poll loop. The server responds with 5xx (crash/restart), 404 (wrong base), 403, or 400 (malformed query — e.g. an unsupported 'types' filter value). A 401 would have thrown AUTH_FAILED; a 200 with {type:'timeout'} continues the loop rather than throwing.","commonSituations":"Server restart mid-poll drops the long-poll connection as a 5xx; wrong base URL; a 'types' filter was passed that the server rejects; reverse proxy timed out the long poll and returned 504; version skew between client and server on the poll query schema.","solutions":["Verify the base URL/port and that the Impeccable Live server (not the app) is reachable.","If passing a 'types' filter, confirm each value is in the server's accepted event-type set.","For intermittent 502/504 behind a proxy, raise the proxy's read timeout above perRequestTimeoutMs (default capped, see PER_REQUEST_TIMEOUT_MS).","Check server logs for the matching request; retry once healthy."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate poll options before entering the loop.\nif (!Array.isArray(types) || types.some((t) => typeof t !== 'string')) {\n  throw new Error('types filter must be a string array');\n}","typeGuard":null,"tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    return await fetchNextEvent(base, token, { types, totalDeadline, signal });\n  } catch (err) {\n    if (err.code === 'AUTH_FAILED' || signal?.aborted) throw err;\n    if (attempt === 2) throw err;\n    await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));\n  }\n}","preventionTips":["Pass a typed string[] for the types filter and confirm each value against the server's accepted set.","Set a totalDeadline so the loop can exit rather than spin on a dead server.","Carry an AbortController signal so you can break long polls on shutdown."],"tags":["network","live-poll","server-error","http","long-poll"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}