paperclipai/paperclip · error · ToolGatewayHttpError

action_request_invalidated

action_request_invalidated

Error message

Tool action request is no longer approvable; refresh the review queue

What it means

Stale-row guard in approveToolAction: the signed arguments payload failed to load/verify (e.g. signing secret changed or payload corrupt) and the request is still pending, so it is cancelled and a 409 returned telling the reviewer the request is no longer approvable and the queue must be refreshed.

Solutions

  1. The request left the approvable state. Refresh the review queue to get current actionable items.
Defensive patterns

Strategy: validation

When it happens

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

Common situations: See trigger scenarios.


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

Appendix: source

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

  }

  async function markApprovedActionFailed(input: {
    actionRequestId: string;
    invocationId: string;
    claimUpdatedAt: Date;
    expectedInvocationStatus: "awaiting_approval" | "executing";
    error: unknown;
  }) {
    const reasonCode =
      input.error instanceof ToolGatewayHttpError
        ? input.error.reasonCode
        : "tool_execution_failed";
    const message =
      input.error instanceof Error ? input.error.message : String(input.error);
    const now = new Date();
    const settled = await db.transaction(async (tx) => {
      // Lock in the same invocation -> request order used by the normal
      // execution settlement path. The claim timestamp is the ownership token:
      // a consumer that merely read an approved row cannot settle another
      // consumer's claim, and a pre-dispatch failure cannot overwrite a call
      // that has already entered provider execution or completed successfully.
      const [invocation] = await tx
        .select({ status: toolInvocations.status })
        .from(toolInvocations)
        .where(eq(toolInvocations.id, input.invocationId))
        .for("update")
        .limit(1);
      if (invocation?.status !== input.expectedInvocationStatus) return false;

      const [actionRequest] = await tx
        .select({
          status: toolActionRequests.status,
          updatedAt: toolActionRequests.updatedAt,
        })
        .from(toolActionRequests)
        .where(eq(toolActionRequests.id, input.actionRequestId))

View on GitHub (pinned to 3f1d897a7c)