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

  1. Read validation.body in the 400 response — it names the exact rule that failed (e.g. appliedEntryIds_must_contain_strings)
  2. Re-send with the documented shape: --data '{"status":"done","appliedEntryIds":["ENTRY_ID"],"failed":[],"files":["src/page.html"],"notes":[]}'
  3. Use status 'partial' (some applied + failed[] populated) or 'error' (zero applied) instead of 'done' when not everything applied
  4. 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

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


AI-assisted analysis of pbakaus/impeccable@f88b2837a7 (2026-08-18). Data as JSON: /api/errors/1ff38251ff593aa9. Report an issue: GitHub.