pbakaus/impeccable · error

AI copy-edit batch did not return a valid completion payload

Error message

AI copy-edit batch did not return a valid completion payload. ${tail.trim()}

What it means

Thrown after the codex/claude subprocess ran to completion: the result.json output was empty, missing, or failed to parse via parseCopyEditBatchResult() (which requires an object with status 'done'|'partial'|'error'). The error appends the last 1200 chars of agent.log (or of the raw output if no log) so the tail surfaces the real reason. The agent process itself did NOT throw — its output was just unparseable.

Source

Thrown at plugin/skills/impeccable/scripts/live-copy-edit-agent.mjs:136

  const outDir = opts.outDir || fs.mkdtempSync(path.join(os.tmpdir(), 'impeccable-copy-batch-'));
  fs.mkdirSync(outDir, { recursive: true });
  const resultPath = path.join(outDir, 'result.json');
  const logPath = path.join(outDir, 'agent.log');

  if (provider === 'codex') {
    await runCodex(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });
  } else if (provider === 'claude') {
    await runClaude(prompt, { cwd, env, resultPath, logPath, timeoutMs: opts.timeoutMs });
  } else {
    throw new Error(`Unsupported live copy-edit AI runner: ${provider}`);
  }

  const output = fs.existsSync(resultPath) ? fs.readFileSync(resultPath, 'utf-8') : '';
  const parsed = parseCopyEditBatchResult(output);
  if (parsed) return parsed;

  const tail = fs.existsSync(logPath) ? fs.readFileSync(logPath, 'utf-8').slice(-1200) : output.slice(-1200);
  throw new Error('AI copy-edit batch did not return a valid completion payload. ' + tail.trim());
}

export function runCopyEditPostApplyChecks({ cwd = process.cwd(), files = [] } = {}) {
  const failures = [];
  const warnings = [];
  const uniqueFiles = [...new Set((files || []).filter((file) => typeof file === 'string' && file.trim()))];
  for (const relativeFile of uniqueFiles) {
    const file = path.resolve(cwd, relativeFile);
    if (!isPathInsideOrEqual(cwd, file) || !fs.existsSync(file)) {
      warnings.push({ file: relativeFile, reason: 'file_missing_or_outside_cwd' });
      continue;
    }
    let content = '';
    try { content = fs.readFileSync(file, 'utf-8'); } catch (err) {
      failures.push({ file: relativeFile, reason: 'read_failed', message: err.message });
      continue;
    }
    const markerMatch = findLeftoverImpeccableMarker(content);

View on GitHub (pinned to d14711ae3d)

Solutions

  1. Read the appended tail — it is the last 1200 chars of agent.log and usually names the real cause (refusal, syntax error, auth replay).
  2. Re-run the Apply; transient model misbehaviour often clears on retry with the same batch.
  3. Verify the codex/claude CLI version matches the flag set in runCodex()/runClaude() (e.g. --output-last-message for codex, --output-format json for claude); upgrade or pin accordingly.
  4. If the model fence-wraps consistently, raise IMPECCABLE_LIVE_COPY_AGENT_EFFORT or switch provider via IMPECCABLE_LIVE_COPY_AGENT so the prompt is honoured more strictly.
  5. Inspect the full outDir (opts.outDir, or the OS tmpdir impeccable-copy-batch-* dir) for result.json and agent.log to diagnose.

Example fix

// before
const result = await runCopyEditBatchAgent(batch, { cwd });

// after
try {
  const result = await runCopyEditBatchAgent(batch, { cwd, outDir: keepForDebugDir });
} catch (err) {
  if (err.message.startsWith('AI copy-edit batch did not return')) {
    console.error('Agent output unparseable. See', keepForDebugDir);
    // inspect result.json + agent.log, then retry or switch provider
  }
  throw err;
}
Defensive patterns

Strategy: try-catch

Try / catch

let result;
try {
  result = await runCopyEditBatchAgent(batch, { cwd, outDir });
} catch (err) {
  if (/did not return a valid completion payload/.test(err.message)) {
    // Inspect outDir/result.json and outDir/agent.log (tail already in the message).
    // Retry once, or switch provider via IMPECCABLE_LIVE_COPY_AGENT.
    console.error('Agent output unparseable. Tail:\\n' + err.message);
    throw err;
  }
  throw err;
}

Prevention

When it happens

Trigger: Agent wrote markdown-fenced JSON despite the prompt's 'ONLY JSON' instruction; agent returned a status field with a different value (e.g. 'success'); result.json never created because the CLI crashed silently with exit code 0; agent emitted only prose explanation; CLI version changed its --output-last-message / --print json format. parseCopyEditAgentResult() also strips ``` fences and extracts the first {...} block, so only genuinely non-JSON or wrong-shape output reaches here.

Common situations: Model 'helpfully' wrapped the JSON in a markdown fence with commentary; older codex/claude CLI whose flags don't match what runCodex/runClaude pass; agent hit a context limit and truncated mid-JSON; agent refused the edit on policy grounds and printed an apology instead of {"status":"error",...}; disk full so result.json write was partial.

Related errors


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