paperclipai/paperclip · error · ToolGatewayHttpError

settledInvocation?.errorCode ?? tool_execution_failed

settledInvocation?.errorCode ?? tool_execution_failed

Error message

settledInvocation?.errorMessage ?? "Approved tool action failed"

What it means

This 502 ToolGatewayHttpError is thrown by replayMatchingAgentAction when an agent retries a governed tool call whose action request is in 'executing' status. The gateway waits for the in-flight execution to settle (waitForActionRequestExecution); if the invocation did not end in 'executed' (failed, errored, or unsettled), the stored invocation error message/errorCode is surfaced as a 502 'Approved tool action failed' (or the invocation's own message), so the retrying agent learns the execution failed rather than hanging.

Solutions

  1. Inspect the error's code (settledInvocation.errorCode) and message to identify why the underlying execution failed; fix that root cause (credentials, parameters, remote connectivity) before retrying.
  2. Change the tool arguments (or add a distinguishing parameter) so the retry does not replay the same failed action request and instead creates a fresh request.
  3. If the remote tool failed transiently, wait for the action request to leave 'executing' and re-issue the call once settled.
  4. Check the remote MCP connection health/auth for the tool; upstream failures are the most common cause of the stored errorMessage.

Example fix

// before: blind retry replays the failed executing request
await toolCall("send_email", sameArgs); // 502 Approved tool action failed
// after: inspect failure, fix, and issue a fresh request
if (err.code === "tool_execution_failed") {
  await fixRemoteConnection();
  await toolCall("send_email", { ...sameArgs, attempt: 2 }); // new argumentsHash → new request
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before retrying the same call, check the previous action request state
const prev = await getMatchingActionRequest({ toolName, argumentsHash });
if (prev?.status === "executed") return prev.result; // replay instead of re-executing
if (prev?.status === "rejected") throw new Error("Action was declined; do not retry the same call");

Type guard

function isReplayableExecution(inv: { status?: string } | null | undefined): inv is { status: "executed" } {
  return !!inv && inv.status === "executed";
}

Try / catch

try {
  return await toolCall(toolName, args);
} catch (err) {
  if (err?.status === 502 && err?.code === "tool_execution_failed") {
    // underlying execution failed; inspect err.message, fix root cause, then
    // vary the arguments to create a fresh action request instead of replaying the failed one
    return toolCall(toolName, { ...args, retryNonce: Date.now() });
  }
  throw err;
}

Prevention

When it happens

Trigger: Retrying a tool call whose arguments hash matches an existing action request already in 'executing' state, where the underlying tool execution failed — e.g. the remote MCP tool returned an error, the execution timed out, or the invocation row recorded an errorCode other than success.

Common situations: An agent retry loop re-issues the same tool call while the first execution fails on the remote provider (network error, auth failure at the upstream tool, invalid parameters rejected by the remote server); a crashed executor leaves the invocation unsettled so the fallback 'tool_execution_failed' code is used.

Related errors


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

Appendix: source

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

        result: storedInvocationResult(invocation),
        invocationId: invocation.id,
      };
    }
    if (actionRequest.status === "executing") {
      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 {
          matched: true as const,
          result: storedInvocationResult(settledInvocation),
          invocationId: invocation.id,
        };
      }
      throw new ToolGatewayHttpError(
        502,
        settledInvocation?.errorMessage ?? "Approved tool action failed",
        settledInvocation?.errorCode ?? "tool_execution_failed",
      );
    }
    if (actionRequest.status === "approved" && actionRequest.decidedAt) {
      const result = await executeApprovedAgentInvocation({
        actionRequest,
        invocation,
      });
      return { matched: true as const, result, invocationId: invocation.id };
    }
    return null;
  }

  /**
   * Project an ask-first test request + its invocation onto the lifecycle the
   * Test tab panel renders. Recovers the redacted parameter snapshot (the

View on GitHub (pinned to 3f1d897a7c)