{"record":{"id":"b78f86ff28d7e618","repo":"paperclipai/paperclip","slug":"settledinvocation-errorcode-tool-execution-failed","errorCode":"settledInvocation?.errorCode ?? tool_execution_failed","errorMessage":"settledInvocation?.errorMessage ?? \"Approved tool action failed\"","messagePattern":"settledInvocation\\?\\.errorMessage \\?\\? \"Approved tool action failed\"","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":502,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":8088,"sourceCode":"        result: storedInvocationResult(invocation),\n        invocationId: invocation.id,\n      };\n    }\n    if (actionRequest.status === \"executing\") {\n      const settled = await waitForActionRequestExecution(actionRequest.id);\n      const [settledInvocation] = await db\n        .select()\n        .from(toolInvocations)\n        .where(eq(toolInvocations.id, invocation.id))\n        .limit(1);\n      if (settled?.status === \"executed\" && settledInvocation) {\n        return {\n          matched: true as const,\n          result: storedInvocationResult(settledInvocation),\n          invocationId: invocation.id,\n        };\n      }\n      throw new ToolGatewayHttpError(\n        502,\n        settledInvocation?.errorMessage ?? \"Approved tool action failed\",\n        settledInvocation?.errorCode ?? \"tool_execution_failed\",\n      );\n    }\n    if (actionRequest.status === \"approved\" && actionRequest.decidedAt) {\n      const result = await executeApprovedAgentInvocation({\n        actionRequest,\n        invocation,\n      });\n      return { matched: true as const, result, invocationId: invocation.id };\n    }\n    return null;\n  }\n\n  /**\n   * Project an ask-first test request + its invocation onto the lifecycle the\n   * Test tab panel renders. Recovers the redacted parameter snapshot (the","sourceCodeStart":8070,"sourceCodeEnd":8106,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L8070-L8106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","If the remote tool failed transiently, wait for the action request to leave 'executing' and re-issue the call once settled.","Check the remote MCP connection health/auth for the tool; upstream failures are the most common cause of the stored errorMessage."],"exampleFix":"// before: blind retry replays the failed executing request\nawait toolCall(\"send_email\", sameArgs); // 502 Approved tool action failed\n// after: inspect failure, fix, and issue a fresh request\nif (err.code === \"tool_execution_failed\") {\n  await fixRemoteConnection();\n  await toolCall(\"send_email\", { ...sameArgs, attempt: 2 }); // new argumentsHash → new request\n}","handlingStrategy":"try-catch","validationCode":"// before retrying the same call, check the previous action request state\nconst prev = await getMatchingActionRequest({ toolName, argumentsHash });\nif (prev?.status === \"executed\") return prev.result; // replay instead of re-executing\nif (prev?.status === \"rejected\") throw new Error(\"Action was declined; do not retry the same call\");","typeGuard":"function isReplayableExecution(inv: { status?: string } | null | undefined): inv is { status: \"executed\" } {\n  return !!inv && inv.status === \"executed\";\n}","tryCatchPattern":"try {\n  return await toolCall(toolName, args);\n} catch (err) {\n  if (err?.status === 502 && err?.code === \"tool_execution_failed\") {\n    // underlying execution failed; inspect err.message, fix root cause, then\n    // vary the arguments to create a fresh action request instead of replaying the failed one\n    return toolCall(toolName, { ...args, retryNonce: Date.now() });\n  }\n  throw err;\n}","preventionTips":["Treat 502 tool_execution_failed as a root-cause signal from the remote tool, not a transient HTTP blip.","Fix upstream connectivity/credentials before retrying identical arguments.","Vary arguments (nonce) when a genuinely fresh attempt is required so a new action request is created.","Cap retries and surface the stored errorCode to the operator instead of looping."],"tags":["http-502","tool-execution","retry","upstream-failure"],"backgroundTag":"upstream-api-error","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}