pbakaus/impeccable · error · Error

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

Error message

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

What it means

Thrown by fetchServerStatus() when GET /status?token=... returns a non-ok, non-401 status. 401 is mapped separately to an AUTH_FAILED error; everything else (500, 404, 503, etc.) becomes this generic message with the raw status code and status text. The status endpoint is polled to check pending events and ack state.

Source

Thrown at plugin/skills/impeccable/scripts/live-poll.mjs:138

  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    const failureLines = Array.isArray(body.failures)
      ? body.failures.map((f) => `  ${f.file}${f.line != null ? `:${f.line}` : ''} ${f.message}`).join('\n')
      : null;
    const parts = [body.error || res.statusText, body.reason, body.hint, failureLines, body._instructions].filter(Boolean);
    throw new Error(parts.join('\n'));
  }
}

export async function fetchServerStatus(base, token) {
  const res = await fetch(`${base}/status?token=${token}`);
  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(`Status failed: ${res.status} ${res.statusText}`);
  }
  return res.json();
}

export function isEventPending(status, eventId) {
  return (status.pendingEvents || []).some((entry) => entry.id === eventId);
}

export async function waitForEventAck(base, token, eventId, {
  pollIntervalMs = 400,
  maxWaitMs = 600_000,
} = {}) {
  const deadline = Date.now() + maxWaitMs;
  while (Date.now() < deadline) {
    const status = await fetchServerStatus(base, token);
    if (!isEventPending(status, eventId)) return true;
    await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
  }

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Restart the live server: stop it (`live-server.mjs stop` or kill the pid in server.json) and start fresh with `live.mjs`.
  2. Confirm server.json (in .impeccable/live/) port matches the actually listening process; re-run setup if stale.
  3. Check the server's stdout/log for the stack trace behind the 5xx.
  4. If a 404, the route may have been renamed in an upgrade — align poll client and server versions.

Example fix

// before
const status = await fetchServerStatus(base, token);

// after
let status;
try {
  status = await fetchServerStatus(base, token);
} catch (err) {
  if (/Status failed: 5\d\d/.test(err.message)) {
    console.error('Server error, restarting live session');
    // stop + restart live.mjs, then retry once
  }
  throw err;
}
Defensive patterns

Strategy: retry

Try / catch

async function safeStatus(base, token, retries = 1) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await fetchServerStatus(base, token);
    } catch (err) {
      if (err.code === 'AUTH_FAILED') throw err;
      if (attempt < retries && /Status failed: 5\d\d/.test(err.message)) {
        await new Promise((r) => setTimeout(r, 500));
        continue;
      }
      throw err;
    }
  }
}

Prevention

When it happens

Trigger: Live server crashed and the port is now served by something else (404); internal server error (500) during status serialisation; the server restarted with in-memory state lost and returns 503; a proxy/firewall returns an unexpected status. Network errors (ECONNREFUSED) surface as fetch rejections, not this message.

Common situations: Server process died but the port wasn't freed; stale server.json pointing at a port a different process now owns; transient overload; version mismatch between poll client and server where the /status route changed.

Related errors


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