pbakaus/impeccable · error
stale_manual_edit_apply_reply
stale_manual_edit_apply_reply
Error message
stale_manual_edit_apply_reply
What it means
Manual-edit applies are hard-timed-out after 150s (IMPECCABLE_LIVE_APPLY_EVENT_HARD_TIMEOUT_MS) and the event id is tombstoned. If the agent's reply arrives after that window, the server treats it as stale: rollbackTimedOutReply reverts the file writes the late apply performed (restoring the pre-apply snapshot), records manual_edit_apply_stale_reply_rejected, and returns HTTP 409 with rolledBackFiles and rollbackFailures counts.
Source
Thrown at skill/scripts/live-server.mjs:1310
failed: summarizeManualApplyFailures(validation.result.failed),
fileCount: validation.result.files.length,
noteCount: validation.result.notes.length,
});
manualApply.resolveDeferred(msg.id, validation.result);
acknowledgePendingEvent(msg.id);
flushPendingPolls();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
return;
}
if (manualApply.hasTimedOutId(msg.id)) {
const rollback = manualApply.rollbackTimedOutReply(msg);
recordManualEditActivity('manual_edit_apply_stale_reply_rejected', {
id: msg.id,
rolledBackFileCount: rollback.rolledBackFiles?.length || 0,
rollbackFailureCount: rollback.rollbackFailures?.length || 0,
});
res.writeHead(409, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'stale_manual_edit_apply_reply', ...rollback }));
return;
}
const sourceEventType = msg.sourceEventType || inferSourceEventType(msg);
if (msg.type === 'retry') {
const releasedEvent = releasePendingEvent(msg.id, sourceEventType);
if (!releasedEvent) {
res.writeHead(msg.id ? 404 : 400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: msg.id ? 'unknown_poll_retry_id' : 'missing_poll_retry_id',
id: msg.id,
}));
return;
}
flushPendingPolls();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true, released: true }));
return;View on GitHub (pinned to f88b2837a7)
Solutions
- Treat the 409 as terminal for that id — do not resend the same reply
- Check rolledBackFileCount and rollbackFailureCount in the response; if rollbackFailures > 0, inspect those source files and git-restore them manually
- Poll again — the server re-issues a fresh apply event id if the edits are still wanted
- For legitimately slow applies, raise IMPECCABLE_LIVE_APPLY_EVENT_HARD_TIMEOUT_MS on the server before starting the next apply
Example fix
// before: reply assumed accepted if HTTP ended
const res = await fetch(replyUrl, { method: 'POST', body: data });
// after: a 409 means the apply was rolled back — start over with a new event
if (res.status === 409) {
const { rolledBackFiles, rollbackFailures } = await res.json();
console.log('stale apply rolled back:', rolledBackFiles, 'failures:', rollbackFailures);
} Defensive patterns
Strategy: validation
Validate before calling
// Before replying, check whether the apply window already closed (default 150s)
const APPLY_TIMEOUT_MS = Number(process.env.IMPECCABLE_LIVE_APPLY_EVENT_HARD_TIMEOUT_MS || 150_000);
if (Date.now() - applyStartedAt > APPLY_TIMEOUT_MS - 5_000) {
// reply would be stale: poll for a fresh event instead of replying
} Try / catch
try {
const res = await reply();
if (res.status === 409) { /* terminal: id timed out, files rolled back — do not resend */ }
} catch (err) { /* network error is NOT staleness; one retry is safe */ } Prevention
- Start the apply clock when the event is received and reply well inside the 150s hard timeout
- Raise IMPECCABLE_LIVE_APPLY_EVENT_HARD_TIMEOUT_MS when the batch is known to be large
- Never blindly re-send a done reply after a long pause — poll first and see what the server expects
When it happens
Trigger: The applier takes longer than 150s to reply (slow disk, huge diff, debugger paused, laptop suspended mid-apply) and its done-message lands after the server already gave up; or a duplicate/delayed delivery of the same reply arrives after the timeout fired.
Common situations: Applying to a project on network filesystem; agent process resumed after suspension; terminal agent retrying the reply command; timeout lowered via env var while the applier legitimately needs longer.
Related errors
AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18).
Data as JSON: /api/errors/e83deb3562dc01e2.
Report an issue: GitHub.