pbakaus/impeccable · error
invalid_manual_apply_result
invalid_manual_apply_result
Error message
invalid_manual_apply_result
What it means
The live server tracks each user-approved manual-edit apply as a pending deferred keyed by event id. When the agent replies (live-poll.mjs --reply <id> done --data '{...}'), validateResultMessage checks the payload shape: status must be 'done'|'partial'|'error', appliedEntryIds must be an array of strings, and failed/files/notes must be arrays; 'partial' with zero applied and zero failed, or 'error' with applied entries, are rejected. On failure the server records manual_edit_apply_reply_invalid and answers HTTP 400 with the validation body, so the apply never completes for that id.
Source
Thrown at skill/scripts/live-server.mjs:1281
}
if (msg.token !== state.token) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Unauthorized' }));
return;
}
const pendingApplyDeferred = manualApply.getDeferred(msg.id);
if (pendingApplyDeferred) {
const validation = manualApply.validateResultMessage(msg, pendingApplyDeferred);
if (!validation.ok) {
recordManualEditActivity('manual_edit_apply_reply_invalid', {
id: msg.id,
pageUrl: pendingApplyDeferred.pageUrl,
chunk: pendingApplyDeferred.event?.chunk || null,
repair: pendingApplyDeferred.event?.repair || null,
reason: validation.body?.reason || validation.body?.error || 'invalid_manual_apply_result',
status: msg.data?.status || null,
});
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(validation.body));
return;
}
recordManualEditActivity('manual_edit_apply_reply_received', {
id: msg.id,
pageUrl: pendingApplyDeferred.pageUrl,
chunk: pendingApplyDeferred.event?.chunk || null,
repair: pendingApplyDeferred.event?.repair || null,
status: validation.result.status,
appliedCount: validation.result.appliedEntryIds.length,
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' });View on GitHub (pinned to f88b2837a7)
Solutions
- Read validation.body in the 400 response — it names the exact rule that failed (e.g. appliedEntryIds_must_contain_strings)
- Re-send with the documented shape: --data '{"status":"done","appliedEntryIds":["ENTRY_ID"],"failed":[],"files":["src/page.html"],"notes":[]}'
- Use status 'partial' (some applied + failed[] populated) or 'error' (zero applied) instead of 'done' when not everything applied
- If the id no longer resolves, poll again — the server re-issues the apply event with a fresh id
Example fix
// before
live-poll.mjs --reply evt_123 done --data '{"status":"done"}'
// after
live-poll.mjs --reply evt_123 done --data '{"status":"done","appliedEntryIds":["evt_123#0"],"failed":[],"files":["src/page.html"],"notes":[]}' Defensive patterns
Strategy: validation
Validate before calling
// Validate the reply shape before sending --reply <id> done --data const isValidApplyResult = (d) => d && typeof d === 'object' && ['done', 'partial', 'error'].includes(d.status) && Array.isArray(d.appliedEntryIds) && d.appliedEntryIds.every((x) => typeof x === 'string') && Array.isArray(d.failed) && Array.isArray(d.files) && Array.isArray(d.notes) && !(d.status === 'error' && d.appliedEntryIds.length > 0) && !(d.status === 'partial' && d.appliedEntryIds.length === 0 && d.failed.length === 0);
Type guard
function isApplyResult(m) { return isValidApplyResult(m); } // narrows to { status: 'done'|'partial'|'error'; appliedEntryIds: string[]; failed: unknown[]; files: string[]; notes: unknown[] } Prevention
- Always generate the --data JSON with JSON.stringify, never hand-concatenated strings
- Copy the exact template from the event instructions: status/appliedEntryIds/failed/files/notes
- Map status to outcome strictly: all applied -> done, some -> partial with failed[] filled, none -> error with empty appliedEntryIds
When it happens
Trigger: Replying to a pending apply event with malformed --data JSON: missing status, appliedEntryIds not an array or containing non-strings, contradictory status ('error' while listing appliedEntryIds, 'partial' with empty applied+failed), or replying with a different message type for the same id.
Common situations: Hand-editing the --data JSON string on the command line (unescaped quotes break the shell/JSON); an agent omitting appliedEntryIds because nothing applied instead of using status:'error'; version skew where an older applier replies without the notes/files fields.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- steer_done_requires_file_or_message
- Status failed: ${res.status} ${res.statusText}
- Poll failed: ${res.status} ${res.statusText}
- Live browser script part missing: ${part.name} (${part.path}
- Not found
AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18).
Data as JSON: /api/errors/1ff38251ff593aa9.
Report an issue: GitHub.