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

  1. Verify the base URL/port and that the Impeccable Live server (not the app) is reachable.
  2. If passing a 'types' filter, confirm each value is in the server's accepted event-type set.
  3. For intermittent 502/504 behind a proxy, raise the proxy's read timeout above perRequestTimeoutMs (default capped, see PER_REQUEST_TIMEOUT_MS).
  4. 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

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


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