paperclipai/paperclip · error
The done report includes unfinished work, failed…
Error message
The done report includes unfinished work, failed verification, or an outstanding decision. Finish the work or report the concrete blocker/reviewer request. No human completion approval was created.
What it means
A 'done' completion report is rejected when its own contents contradict completion: any remaining-work entry with blocksCompletion=true, a verification signal with status 'failed', or any actionable attention requests. Paperclip refuses to create a human completion approval from a self-contradictory done report.
Solutions
- Resolve or remove blocking remainingWork entries, or mark them non-blocking only if genuinely complete.
- Fix the failing verification checks before reporting done, or report blocked with the concrete failure.
- Resolve all attention requests (or reclassify them as non-actionable) before claiming done.
- Report a non-done disposition (blocked/needs follow-up) until the report is internally consistent with completion.
Example fix
// before
result.completionClaim.remainingWork.push({ description: "flaky test", blocksCompletion: true });
result.reportedWorkDisposition = "done";
// after
result.completionClaim.remainingWork = result.completionClaim.remainingWork.filter(w => !w.blocksCompletion);
// or report blocked if the flaky test still stands
result.reportedWorkDisposition = "blocked"; Defensive patterns
Strategy: validation
Validate before calling
const blocking = result.completionClaim.remainingWork.some(w => w.blocksCompletion);
const failedCheck = result.verificationSignals.some(v => v.status === "failed");
if (result.reportedWorkDisposition === "done" && (blocking || failedCheck || result.attentionRequests.length > 0)) {
throw new Error("done report contradicts its own contents");
} Try / catch
try { await reportCompletion({ runId, result }); } catch (e) { if (e.message.startsWith("The done report includes unfinished work")) { result.reportedWorkDisposition = "blocked"; return reportCompletion({ runId, result }); } throw e; } Prevention
- Reconcile remainingWork and verification results before choosing the disposition.
- Only set blocksCompletion on entries that genuinely block; drop resolved entries.
- Resolve or close attention requests before claiming done.
When it happens
Trigger: Agent reports disposition 'done' while result.completionClaim.remainingWork contains a blocking entry; verification checks reported status 'failed'; normalizePrpResultSignals yields actionableAttentionRequests.length > 0 alongside a done claim.
Common situations: Agents summarizing leftovers in remainingWork but still claiming done; failing test runs included in the verification section of an otherwise done report; attention requests (questions, reviewer asks) left open while claiming completion.
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
- Durable PRP run identity rotation is invalid.
- needs_review requires a concrete decision and a named…
- Resolve your assigned review with resolve_review before…
- A full lowercase source SHA is required.
- A reusable lease cannot be replaced and reacquired in the…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/1c4c824d638cc65e.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/native-runtime/native-completion-feedback.ts:71
}
return review?.interaction.status === "pending"
? "Review blocker recorded. Paperclip will preserve the task and record the reviewer recovery action."
: "Review report accepted. The recorded review decision controls task completion; this report cannot override it.";
}
const signals = normalizePrpResultSignals(result);
if (
result.reportedWorkDisposition === "done" &&
(!result.completionClaim.objectiveSatisfied ||
result.completionClaim.criteria.some(
(entry) => entry.status !== "satisfied",
) ||
result.completionClaim.remainingWork.some(
(entry) => entry.blocksCompletion,
) ||
signals.verification.some((entry) => entry.status === "failed") ||
signals.actionableAttentionRequests.length > 0)
) {
throw new Error(
"The done report includes unfinished work, failed verification, or an outstanding decision. Finish the work or report the concrete blocker/reviewer request. No human completion approval was created.",
);
}
if (["done", "cancelled"].includes(issue.status)) {
return `Report accepted; task is already ${issue.status}. This report will not reopen it.`;
}
if (issue.executionRunId && issue.executionRunId !== runId) {
return "Report accepted; a newer run owns the task. Do not claim this report changed its status.";
}
const continuation = run.contextSnapshot?.executionContinuation as { objective?: unknown } | undefined;
const objective = typeof continuation?.objective === "string"
? continuation.objective : [issue.title, issue.description].filter(Boolean).join("\n");
await validateNativeDeliverableEvidence(db, { companyId: run.companyId, issueId: issue.id, runId,
objective, semanticToolReceipts: run.resultJson?.semanticToolReceipts }, result);
const retiredCandidates = await findAutomaticCompletionReviews(db, issue.id);
const retiredIds = retiredCandidates.map(({ interaction }) => interaction.id);
const [interaction, approval] = await Promise.all([
dbView on GitHub (pinned to 3f1d897a7c)