paperclipai/paperclip · error · ToolGatewayHttpError

action_already_consumed

action_already_consumed

Error message

Tool action request was already consumed

What it means

Idempotency guard in executeApprovedAgentInvocation: the compare-and-set from approved to executing matched no rows, meaning another executor already consumed this approval (or it was resolved otherwise) and the wait-for-settlement fallback also found it neither executed nor failed. The request was already consumed and cannot run twice.

Solutions

  1. This action request was already consumed by a prior execution. Create a new approval request instead of reusing it.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/src/services/tool-gateway.ts:4177 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

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

    if (!claimed) {
      const settled = await waitForActionRequestExecution(actionRequest.id);
      const [settledInvocation] = await db
        .select()
        .from(toolInvocations)
        .where(eq(toolInvocations.id, invocation.id))
        .limit(1);
      if (settled?.status === "executed" && settledInvocation) {
        return storedInvocationResult(settledInvocation);
      }
      if (settled?.status === "failed") {
        throw new ToolGatewayHttpError(
          502,
          settledInvocation?.errorMessage ?? "Approved tool action failed",
          settledInvocation?.errorCode ?? "tool_execution_failed",
          { actionRequestId: actionRequest.id, invocationId: invocation.id },
        );
      }
      throw new ToolGatewayHttpError(
        409,
        "Tool action request was already consumed",
        "action_already_consumed",
      );
    }

    // Terminal-issue expiry revokes pending/approved requests, but a claim that
    // committed just before the issue closed slips past that revocation. Recheck
    // the issue after winning the claim so a governed action never runs external
    // side effects for an issue that is already done or cancelled.
    const issue = await assertIssueOpenForApprovedAction({
      claimed,
      invocation,
    });

    const signedPayload = readSignedToolArgumentsPayload({
      signedArguments: claimed.signedArguments,
      invocationId: invocation.id,

View on GitHub (pinned to 3f1d897a7c)