{"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":6778,"sourceCode":"          await db.update(toolInvocations).set({ status: \"failed\", errorCode, errorMessage, completedAt: now, updatedAt: now }).where(eq(toolInvocations.id, row.invocationId));\n          await reflectToolActionInteractionLifecycle({ actionRequestId: row.id, status, errorCode, errorMessage });\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) throw new ToolGatewayHttpError(403, \"Only a human can resolve a tool review\", \"human_review_required\");\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(404, \"Tool action request not found\", \"action_request_not_found\");\n      }\n      const [invocation] = await db\n        .select()\n        .from(toolInvocations)\n        .where(eq(toolInvocations.id, actionRequest.invocationId))\n        .limit(1);\n      if (!invocation || invocation.companyId !== input.companyId) {\n        throw new ToolGatewayHttpError(404, \"Tool invocation not found\", \"invocation_not_found\");\n      }\n      if (input.issueId !== undefined || input.interactionId !== undefined) {\n        if (","sourceCodeStart":6760,"sourceCodeEnd":6796,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/services/tool-gateway.ts#L6760-L6796","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":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}