{"record":{"id":"0ffa32e9d2c15355","repo":"paperclipai/paperclip","slug":"human-review-required","errorCode":"human_review_required","errorMessage":"Only a human can resolve a tool review","messagePattern":"Only a human can resolve a tool review","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":403,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":9251,"sourceCode":"          });\n        }\n        scanned += rows.length;\n        if (rows.length < 100) break;\n        cursor = rows[rows.length - 1].id;\n      }\n      return { scanned };\n    },\n\n    async approveActionRequest(input: {\n      companyId: string;\n      rememberAction?: boolean;\n      issueId?: string;\n      interactionId?: string;\n      actionRequestId: string;\n      actor: { agentId?: string | null; userId?: string | null };\n    }) {\n      if (input.actor.agentId)\n        throw new ToolGatewayHttpError(\n          403,\n          \"Only a human can resolve a tool review\",\n          \"human_review_required\",\n        );\n      const [actionRequest] = await db\n        .select()\n        .from(toolActionRequests)\n        .where(eq(toolActionRequests.id, input.actionRequestId))\n        .limit(1);\n      if (!actionRequest || actionRequest.companyId !== input.companyId) {\n        throw new ToolGatewayHttpError(\n          404,\n          \"Tool action request not found\",\n          \"action_request_not_found\",\n        );\n      }\n      const [invocation] = await db\n        .select()","sourceCodeStart":9233,"sourceCodeEnd":9269,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L9233-L9269","documentation":"Tool review resolution (approve/reject of a tool action request) is a human-only governance gate. If the actor carries an agentId, the gateway immediately rejects with 403 — agents may not resolve their own approval requests.","triggerScenarios":"Calling the resolveToolActionReview endpoint/function with input.actor.agentId set (agent bearer key) instead of a userId (board/human session).","commonSituations":"Agent automation tries to auto-approve its own parked tool call; a script uses an agent API key where an operator session is required; misconfigured job passes the agent identity into the review resolver.","solutions":["Call the review resolution with a human actor (board session / userId, no agentId)","Have a human approve via the board UI or an operator-authenticated API call","Remove the agentId from input.actor if the caller is actually a human acting on behalf"],"exampleFix":"// before\nawait resolveToolActionReview({ companyId, actionRequestId, actor: { agentId } });\n// after\nawait resolveToolActionReview({ companyId, actionRequestId, actor: { userId: operatorUserId } });","handlingStrategy":"validation","validationCode":"if (actor.agentId) throw new Error('Review resolution requires a human actor (userId), not an agent key');","typeGuard":"const isHumanActor = (a: { agentId?: string | null; userId?: string | null }) => !a.agentId && !!a.userId;","tryCatchPattern":null,"preventionTips":["Use board/operator sessions for approval endpoints, never agent API keys","Route any desired auto-approval through a human-configured policy rule instead","Review automation scripts for leaked agent credentials"],"tags":["governance","approval","agents"],"backgroundTag":"insufficient-permissions","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"}