{"record":{"id":"1c4c824d638cc65e","repo":"paperclipai/paperclip","slug":"the-done-report-includes-unfinished-work-failed-verification","errorCode":null,"errorMessage":"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.","messagePattern":"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\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/native-completion-feedback.ts","lineNumber":71,"sourceCode":"    }\n    return review?.interaction.status === \"pending\"\n      ? \"Review blocker recorded. Paperclip will preserve the task and record the reviewer recovery action.\"\n      : \"Review report accepted. The recorded review decision controls task completion; this report cannot override it.\";\n  }\n  const signals = normalizePrpResultSignals(result);\n  if (\n    result.reportedWorkDisposition === \"done\" &&\n    (!result.completionClaim.objectiveSatisfied ||\n      result.completionClaim.criteria.some(\n        (entry) => entry.status !== \"satisfied\",\n      ) ||\n      result.completionClaim.remainingWork.some(\n        (entry) => entry.blocksCompletion,\n      ) ||\n      signals.verification.some((entry) => entry.status === \"failed\") ||\n      signals.actionableAttentionRequests.length > 0)\n  ) {\n    throw new Error(\n      \"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.\",\n    );\n  }\n  if ([\"done\", \"cancelled\"].includes(issue.status)) {\n    return `Report accepted; task is already ${issue.status}. This report will not reopen it.`;\n  }\n  if (issue.executionRunId && issue.executionRunId !== runId) {\n    return \"Report accepted; a newer run owns the task. Do not claim this report changed its status.\";\n  }\n  const continuation = run.contextSnapshot?.executionContinuation as { objective?: unknown } | undefined;\n  const objective = typeof continuation?.objective === \"string\"\n    ? continuation.objective : [issue.title, issue.description].filter(Boolean).join(\"\\n\");\n  await validateNativeDeliverableEvidence(db, { companyId: run.companyId, issueId: issue.id, runId,\n    objective, semanticToolReceipts: run.resultJson?.semanticToolReceipts }, result);\n  const retiredCandidates = await findAutomaticCompletionReviews(db, issue.id);\n  const retiredIds = retiredCandidates.map(({ interaction }) => interaction.id);\n  const [interaction, approval] = await Promise.all([\n    db","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/native-completion-feedback.ts#L53-L89","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nresult.completionClaim.remainingWork.push({ description: \"flaky test\", blocksCompletion: true });\nresult.reportedWorkDisposition = \"done\";\n// after\nresult.completionClaim.remainingWork = result.completionClaim.remainingWork.filter(w => !w.blocksCompletion);\n// or report blocked if the flaky test still stands\nresult.reportedWorkDisposition = \"blocked\";","handlingStrategy":"validation","validationCode":"const blocking = result.completionClaim.remainingWork.some(w => w.blocksCompletion);\nconst failedCheck = result.verificationSignals.some(v => v.status === \"failed\");\nif (result.reportedWorkDisposition === \"done\" && (blocking || failedCheck || result.attentionRequests.length > 0)) {\n  throw new Error(\"done report contradicts its own contents\");\n}","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["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."],"tags":["validation","completion-report","state-machine"],"backgroundTag":"schema-validation-failed","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}