pbakaus/impeccable · error
Poll failed: ${res.status} ${res.statusText}
Error message
Poll failed: ${res.status} ${res.statusText} What it means
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.
Source
Thrown at skill/scripts/live-poll.mjs:193
: PER_REQUEST_TIMEOUT_MS;
const slice = Math.min(Math.max(remaining, 1000), perRequestTimeoutMs);
const query = new URLSearchParams({
token,
timeout: String(slice),
leaseMs: String(leaseMs),
});
const normalizedTypes = normalizePollTypes(resolveTypes ? await resolveTypes() : types);
if (normalizedTypes.length > 0) query.set('types', normalizedTypes.join(','));
const res = await fetch(`${base}/poll?${query}`, { signal });
if (res.status === 401) {
const err = new Error('Authentication failed. The server token may have changed.');
err.code = 'AUTH_FAILED';
throw err;
}
if (!res.ok) {
throw new Error(`Poll failed: ${res.status} ${res.statusText}`);
}
const next = await res.json();
if (next?.type === 'timeout') {
if (totalDeadline && Date.now() < totalDeadline) continue;
if (!totalDeadline) continue;
return next;
}
return next;
}
}
export async function augmentEventWithAcceptHandling(event, base, token) {
if (event.type !== 'accept' && event.type !== 'discard') return event;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const acceptScript = path.join(__dirname, 'live-accept.mjs');
const scriptArgs = buildAcceptScriptArgs(event);View on GitHub (pinned to d14711ae3d)
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.
Defensive patterns
Strategy: retry
Validate before calling
// Validate poll options before entering the loop.
if (!Array.isArray(types) || types.some((t) => typeof t !== 'string')) {
throw new Error('types filter must be a string array');
} Try / catch
for (let attempt = 0; attempt < 3; attempt++) {
try {
return await fetchNextEvent(base, token, { types, totalDeadline, signal });
} catch (err) {
if (err.code === 'AUTH_FAILED' || signal?.aborted) throw err;
if (attempt === 2) throw err;
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Poll failed: ${res.status} ${res.statusText}
- ${parts.join('\n')}
- Status failed: ${res.status} ${res.statusText}
- ${body.error || res.statusText}\n${body.reason}\n${body.hint
- Status failed: ${res.status} ${res.statusText}
AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13).
Data as JSON: /api/errors/71572829245c0089.
Report an issue: GitHub.