paperclipai/paperclip · error · ToolGatewayHttpError

human_review_required

human_review_required

Error message

Only a human can resolve a tool review

What it means

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.

Solutions

  1. Call the review resolution with a human actor (board session / userId, no agentId)
  2. Have a human approve via the board UI or an operator-authenticated API call
  3. Remove the agentId from input.actor if the caller is actually a human acting on behalf

Example fix

// before
await resolveToolActionReview({ companyId, actionRequestId, actor: { agentId } });
// after
await resolveToolActionReview({ companyId, actionRequestId, actor: { userId: operatorUserId } });
Defensive patterns

Strategy: validation

Validate before calling

if (actor.agentId) throw new Error('Review resolution requires a human actor (userId), not an agent key');

Type guard

const isHumanActor = (a: { agentId?: string | null; userId?: string | null }) => !a.agentId && !!a.userId;

Prevention

When it happens

Trigger: Calling the resolveToolActionReview endpoint/function with input.actor.agentId set (agent bearer key) instead of a userId (board/human session).

Common situations: 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.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/0ffa32e9d2c15355. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/tool-gateway.ts:9251

          });
        }
        scanned += rows.length;
        if (rows.length < 100) break;
        cursor = rows[rows.length - 1].id;
      }
      return { scanned };
    },

    async approveActionRequest(input: {
      companyId: string;
      rememberAction?: boolean;
      issueId?: string;
      interactionId?: string;
      actionRequestId: string;
      actor: { agentId?: string | null; userId?: string | null };
    }) {
      if (input.actor.agentId)
        throw new ToolGatewayHttpError(
          403,
          "Only a human can resolve a tool review",
          "human_review_required",
        );
      const [actionRequest] = await db
        .select()
        .from(toolActionRequests)
        .where(eq(toolActionRequests.id, input.actionRequestId))
        .limit(1);
      if (!actionRequest || actionRequest.companyId !== input.companyId) {
        throw new ToolGatewayHttpError(
          404,
          "Tool action request not found",
          "action_request_not_found",
        );
      }
      const [invocation] = await db
        .select()

View on GitHub (pinned to 3f1d897a7c)