pbakaus/impeccable · error · Error

${body.error || res.statusText}\n${body.reason}\n${body.hint

Error message

${body.error || res.statusText}\n${body.reason}\n${body.hint}\n${failureLines}\n${body._instructions}

What it means

Thrown by postReply() when the POST /poll reply gets a non-ok HTTP status. The error message is a join of all truthy parts from the JSON error body: body.error (or res.statusText), body.reason, body.hint, formatted failure lines (file:line message), and body._instructions. This is the server's structured rejection of a reply (e.g. a failed manual_edit_apply result).

Source

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

}

export function requiresAgentReply(event) {
  return EVENT_TYPES_NEEDING_AGENT_REPLY.has(event?.type);
}

export async function postReply(base, token, reply) {
  const res = await fetch(`${base}/poll`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(buildPollReplyPayload(token, reply)),
  });
  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);

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Read the joined parts: body.reason names the class (e.g. validation_failed, lease_expired), body.hint gives the next step, and failureLines list per-file failures to fix.
  2. For manual_edit_apply: re-open the listed source files, fix the failures (leftover markers, syntax), and re-Apply — the buffer re-stages.
  3. If the lease expired, the server dropped the event; wait for or trigger a new one rather than re-replying to the same id.
  4. If body._instructions is present it carries the authoritative next command with real ids — follow it.
  5. Confirm you are replying to the correct event id with the correct token (read from .impeccable/live/server.json).

Example fix

// before
await postReply(base, token, { id, type: 'done' });

// after
try {
  await postReply(base, token, { id, type: 'done', data: resultJson });
} catch (err) {
  console.error('Reply rejected:\\n' + err.message);
  // err.message already contains reason, hint, failure lines, instructions
  process.exit(1);
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await postReply(base, token, reply);
} catch (err) {
  // err.message already joins body.error, reason, hint, failure lines, _instructions.
  const isLeaseExpired = /lease/i.test(err.message);
  const isValidation = /validation|failed/i.test(err.message);
  if (isValidation) { /* fix source per failure lines, re-Apply */ }
  else if (isLeaseExpired) { /* wait for a new event, don't re-reply */ }
  else { console.error('Reply rejected:', err.message); process.exit(1); }
}

Prevention

When it happens

Trigger: Replying 'done' to a manual_edit_apply event but the server's validation of appliedEntryIds/files fails (body.failures populated); replying to an event the server no longer considers leased (expired lease -> 410); malformed reply payload the server rejects with 400; replying to an already-acknowledged event id. The server returns whatever status/reason it chose.

Common situations: Agent's applied source failed post-apply checks (leftover impeccable markers, invalid JSON, syntax error) so the server rejects the done reply; lease expired (DEFAULT_EVENT_LEASE_MS = 600s) while the agent was working; two pollers raced and one already replied; token mismatch causing a non-401 error.

Related errors


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