pbakaus/impeccable · error · Error

Poll failed

Error message

Poll failed: ${res.status} ${res.statusText}

What it means

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.

Solutions

  1. Check the live-server console — 5xx statuses almost always have a matching stack trace there
  2. If a proxy fronts the server, raise its proxy_read_timeout above the poll slice so idle long polls are not cut into 504s
  3. Verify base and token still match the running server (a mismatch shows up as 401/AUTH_FAILED, which is handled separately)
  4. Retry fetchNextEvent with backoff — a transient 5xx during a server reload or while it is busy scaffolding is expected to clear
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight the poll endpoint once before entering the long-poll loop:
const probe = await fetch(`${base}/poll?token=${token}&timeout=1000`);
if (probe.status === 401) throw new Error('token mismatch — re-read server token');
if (!probe.ok) throw new Error(`poll endpoint unhealthy: HTTP ${probe.status}`);

Try / catch

while (running) {
  try {
    event = await fetchNextEvent(base, token, { signal });
  } catch (err) {
    if (err?.code === 'AUTH_FAILED') throw err;            // stop: fix the token
    if (!/^Poll failed: 5\d\d/.test(err?.message || '')) throw err;
    await backoff();                                        // transient 5xx: retry the loop
    continue;
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/71572829245c0089. Report an issue: GitHub.

Appendix: 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 f88b2837a7)