{"record":{"id":"271e30c522ebba92","repo":"pbakaus/impeccable","slug":"body-error-res-statustext-n-body-reason-n","errorCode":null,"errorMessage":"${body.error || res.statusText}\\n${body.reason}\\n${body.hint}\\n${failureLines}\\n${body._instructions}","messagePattern":"(.+?)\\\\n(.+?)\\\\n(.+?)\\\\n(.+?)\\\\n(.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugin/skills/impeccable/scripts/live-poll.mjs","lineNumber":126,"sourceCode":"}\n\nexport function requiresAgentReply(event) {\n  return EVENT_TYPES_NEEDING_AGENT_REPLY.has(event?.type);\n}\n\nexport async function postReply(base, token, reply) {\n  const res = await fetch(`${base}/poll`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify(buildPollReplyPayload(token, reply)),\n  });\n  if (!res.ok) {\n    const body = await res.json().catch(() => ({}));\n    const failureLines = Array.isArray(body.failures)\n      ? body.failures.map((f) => `  ${f.file}${f.line != null ? `:${f.line}` : ''} ${f.message}`).join('\\n')\n      : null;\n    const parts = [body.error || res.statusText, body.reason, body.hint, failureLines, body._instructions].filter(Boolean);\n    throw new Error(parts.join('\\n'));\n  }\n}\n\nexport async function fetchServerStatus(base, token) {\n  const res = await fetch(`${base}/status?token=${token}`);\n  if (res.status === 401) {\n    const err = new Error('Authentication failed. The server token may have changed.');\n    err.code = 'AUTH_FAILED';\n    throw err;\n  }\n  if (!res.ok) {\n    throw new Error(`Status failed: ${res.status} ${res.statusText}`);\n  }\n  return res.json();\n}\n\nexport function isEventPending(status, eventId) {\n  return (status.pendingEvents || []).some((entry) => entry.id === eventId);","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/pbakaus/impeccable/blob/d14711ae3d1a1dd62dee61a358d27f107c51ccd0/plugin/skills/impeccable/scripts/live-poll.mjs#L108-L144","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","For manual_edit_apply: re-open the listed source files, fix the failures (leftover markers, syntax), and re-Apply — the buffer re-stages.","If the lease expired, the server dropped the event; wait for or trigger a new one rather than re-replying to the same id.","If body._instructions is present it carries the authoritative next command with real ids — follow it.","Confirm you are replying to the correct event id with the correct token (read from .impeccable/live/server.json)."],"exampleFix":"// before\nawait postReply(base, token, { id, type: 'done' });\n\n// after\ntry {\n  await postReply(base, token, { id, type: 'done', data: resultJson });\n} catch (err) {\n  console.error('Reply rejected:\\\\n' + err.message);\n  // err.message already contains reason, hint, failure lines, instructions\n  process.exit(1);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await postReply(base, token, reply);\n} catch (err) {\n  // err.message already joins body.error, reason, hint, failure lines, _instructions.\n  const isLeaseExpired = /lease/i.test(err.message);\n  const isValidation = /validation|failed/i.test(err.message);\n  if (isValidation) { /* fix source per failure lines, re-Apply */ }\n  else if (isLeaseExpired) { /* wait for a new event, don't re-reply */ }\n  else { console.error('Reply rejected:', err.message); process.exit(1); }\n}","preventionTips":["Reply within DEFAULT_EVENT_LEASE_MS (600s) — long agent runs must keep the lease alive or risk a rejection.","For manual_edit_apply, ensure post-apply source is clean (no impeccable markers, valid JSON/syntax) before replying done.","Parse err.message for reason/hint/_instructions rather than matching on status codes — the structured body carries the action."],"tags":["live-poll","network","http","server-rejection"],"backgroundTag":null,"analyzedSha":"d14711ae3d1a1dd62dee61a358d27f107c51ccd0","analyzedAt":"2026-08-13T00:52:25.771Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}