{"record":{"id":"4acb39ce1623e251","repo":"paperclipai/paperclip","slug":"resolve-your-assigned-review-with-resolve-review-before","errorCode":null,"errorMessage":"Resolve your assigned review with resolve_review before finishing. If you cannot review the work, report the concrete blocker with paperclip_block.","messagePattern":"Resolve your assigned review with resolve_review before finishing\\. If you cannot review the work, report the concrete blocker with paperclip_block\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/src/services/native-runtime/native-completion-feedback.ts","lineNumber":52,"sourceCode":"  const issue = await db\n    .select()\n    .from(issues)\n    .where(\n      and(\n        eq(issues.id, run.nativeIssueId),\n        eq(issues.companyId, run.companyId),\n      ),\n    )\n    .then((rows) => rows[0]);\n  if (!issue) throw new Error(\"Completion task no longer exists.\");\n  const reviewContext = readNativeReviewAssignmentContext(run.contextSnapshot);\n  if (reviewContext) {\n    const review = await getNativeReviewAssignment(db, {\n      companyId: run.companyId, issueId: issue.id, agentId: run.agentId,\n      contextSnapshot: reviewContext, allowResolvedByRunId: run.id,\n    });\n    if (review?.interaction.status === \"pending\" && result.reportedWorkDisposition !== \"blocked\") {\n      throw new Error(\"Resolve your assigned review with resolve_review before finishing. If you cannot review the work, report the concrete blocker with paperclip_block.\");\n    }\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  ) {","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/native-runtime/native-completion-feedback.ts#L34-L70","documentation":"When the run's context snapshot contains a review assignment, the agent must resolve that review before the task can complete. nativeCompletionFeedback throws if the review interaction is still pending and the agent did not report a 'blocked' work disposition. This enforces the review gate: a pending reviewer cannot just report done.","triggerScenarios":"An agent with an assigned review calls the completion/feedback API with reportedWorkDisposition != 'blocked' while the review interaction status is 'pending'; resolve_review was never invoked or its result wasn't persisted before finishing.","commonSituations":"Reviewer agents skipping the resolve_review step; agent runs finishing on a timer/turn limit before resolving the review; adapters reporting done generically without checking for pending review interactions in the context snapshot.","solutions":["Call resolve_review with a concrete decision before reporting completion.","If the work cannot be reviewed, report with reportedWorkDisposition: 'blocked' and a concrete blocker instead of done.","Inspect the run's contextSnapshot for a review assignment early in the run and handle it before attempting completion.","Check the resolve_review call actually succeeded and persisted (interaction status no longer 'pending')."],"exampleFix":"// before\nawait reportCompletion({ runId, result: { reportedWorkDisposition: \"done\" } });\n// after\nif (hasPendingReview(contextSnapshot)) {\n  await resolveReview({ runId, decision: \"approve\" }); // or report blocked\n}\nawait reportCompletion({ runId, result: { reportedWorkDisposition: \"done\" } });","handlingStrategy":"validation","validationCode":"const reviewCtx = readNativeReviewAssignmentContext(run.contextSnapshot);\nif (reviewCtx && review.interaction.status === \"pending\" && disposition !== \"blocked\") {\n  throw new Error(\"resolve the pending review (resolve_review) or report blocked before completing\");\n}","typeGuard":"const hasPendingReview = (s: unknown): boolean => { const ctx = readNativeReviewAssignmentContext(s); return !!ctx && ctx.interaction.status === \"pending\"; };","tryCatchPattern":"try { await reportCompletion({ runId, result }); } catch (e) { if (e.message.startsWith(\"Resolve your assigned review\")) { await resolveReview({ runId, decision: /* concrete decision */ }); return reportCompletion({ runId, result }); } throw e; }","preventionTips":["Check the context snapshot for review assignments at run start, not at completion time.","Always invoke resolve_review with a decision before any completion report.","Use reportedWorkDisposition 'blocked' with a concrete blocker when the review cannot be done."],"tags":["state-machine","review-gate","completion-report"],"backgroundTag":"invalid-state-transition","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"}