{"record":{"id":"27d91e4da80c7c2d","repo":"paperclipai/paperclip","slug":"currentaccess-reasoncode","errorCode":"currentAccess.reasonCode","errorMessage":"currentAccess.explanation","messagePattern":"currentAccess\\.explanation","errorType":"http","errorClass":"ToolGatewayHttpError","httpStatus":403,"severity":"error","filePath":"server/src/services/tool-gateway.ts","lineNumber":7720,"sourceCode":"        signedPayload.identityContextId,\n      );\n      tool = await findToolForSession(session, invocation.toolName);\n      liveApprovalSnapshot = await connectedRemoteApprovalSnapshot(\n        session,\n        tool,\n      );\n      const currentAccess = await policyService.decide(\n        policyInputForTool({\n          session,\n          tool,\n          parameters: signedPayload.arguments,\n        }),\n      );\n      if (\n        !currentAccess.allowed &&\n        currentAccess.decision !== \"require_approval\"\n      )\n        throw new ToolGatewayHttpError(\n          403,\n          currentAccess.explanation,\n          currentAccess.reasonCode,\n        );\n    } catch (error) {\n      await markApprovedActionFailed({\n        actionRequestId: claimed.id,\n        invocationId: invocation.id,\n        claimUpdatedAt: claimed.updatedAt,\n        expectedInvocationStatus: \"awaiting_approval\",\n        error,\n      });\n      throw error;\n    }\n    if (\n      !approvalSnapshotsMatch(\n        signedPayload.approvalSnapshot,\n        liveApprovalSnapshot,","sourceCodeStart":7702,"sourceCodeEnd":7738,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/server/src/services/tool-gateway.ts#L7702-L7738","documentation":"This 403 ToolGatewayHttpError is thrown during execution of an approved (execute-on-approve) tool action, after the request claim is won and the signed arguments are verified. Before running the side effect, the policy service is re-consulted (policyService.decide) with the current session, tool, and parameters; if the live policy decision is not allowed and not 'require_approval', the action is aborted with the policy's explanation and reasonCode, and the action request is marked failed. This enforces that policy revocations between approval time and execution time are honored.","triggerScenarios":"Executing a claimed approved tool action where the company/agent tool policy has changed since approval: the tool was moved to a denied policy class, the agent lost permission for the tool or connection, budget/domain/parameter-level policy rules now deny the call, or the identity context restored for the action is no longer permitted the tool.","commonSituations":"An operator tightened tool permissions while the approval sat in the queue; the approval was granted under one policy snapshot but a policy update landed before execution; a remote MCP connection's permission set changed so the re-decision denies; token/role downgrades on the agent between approve and execute.","solutions":["Read currentAccess.explanation/reasonCode in the error to see which policy rule denied the action, then adjust that policy (or the tool/connection config) and have the agent re-request approval.","Re-request the tool action so a fresh approval is created and decided under the current policy.","If the deny is unintended, update the tool policy for the company/agent to allow the tool class, then retry the invocation.","Check whether the tool or its remote connection definition changed after approval; if so a new review is required regardless."],"exampleFix":"// before: policy denies post-approval, action fails\nawait executeApprovedAction(actionRequestId);\n// after: verify policy before requesting approval to avoid wasted approvals\nconst access = await policyService.decide(policyInputForTool({ session, tool, parameters }));\nif (!access.allowed && access.decision !== \"require_approval\") throw new Error(`Policy denies tool: ${access.explanation}`);\nawait executeApprovedAction(actionRequestId);","handlingStrategy":"try-catch","validationCode":"// pre-flight: re-check policy before requesting approval\nconst access = await policyService.decide(policyInputForTool({ session, tool, parameters }));\nif (!access.allowed && access.decision !== \"require_approval\") {\n  throw new Error(`Tool policy denies this action: ${access.explanation}`);\n}","typeGuard":"function isPolicyAllowed(access: { allowed: boolean; decision: string }): boolean {\n  return access.allowed || access.decision === \"require_approval\";\n}","tryCatchPattern":"try {\n  await executeApprovedAction(actionRequestId);\n} catch (err) {\n  if (err?.status === 403 && typeof err?.code === \"string\") {\n    // policy changed between approval and execution; mark request failed and re-request\n    await requestFreshApproval({ toolName, parameters, note: err.message });\n  } else throw err;\n}","preventionTips":["Avoid editing tool policies while approvals are pending; batch policy changes between review cycles.","Keep approval queues short so the gap between approval and execution stays small.","Log reasonCode/explanation from 403s to spot which policy rules repeatedly block actions.","Re-validate policy at request time so stale requests are rejected early."],"tags":["authorization","http-403","policy","approval-gate"],"backgroundTag":"permission-denied","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}