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
- Read the appended tail — it is the last 1200 chars of agent.log and usually names the real cause (refusal, syntax error, auth replay).
- Re-run the Apply; transient model misbehaviour often clears on retry with the same batch.
- 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.
- 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.
- 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
- Pass a stable opts.outDir so you can inspect result.json and agent.log after a failure.
- Keep codex/claude CLI versions aligned with the flags runCodex()/runClaude() pass (codex --output-last-message, claude --output-format json).
- If the model routinely wraps JSON in fences, switch provider or raise IMPECCABLE_LIVE_COPY_AGENT_EFFORT for stricter adherence.
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
- No live copy-edit AI runner is available.
- Unsupported live copy-edit AI runner: ${provider}
- No live copy-edit AI runner is available.
- Unsupported live copy-edit AI runner: ${provider}
- AI copy-edit batch did not return a valid completion payload
AI-assisted analysis of pbakaus/impeccable@d14711ae3d (2026-08-13).
Data as JSON: /api/errors/c56ee20a7e77462e.
Report an issue: GitHub.