A Claude Code worker could inspect a failed release and retry one CI job. It could not deploy to production.

The policy looked sensible:

production.deploy       denied
production.write        denied
ci.job.read              allowed
ci.job.retry             allowed

The worker retried package-and-verify. The job passed, published an artifact, and emitted a signed release event. A deployment workflow woke up under the release service account. Production changed 94 seconds after the permitted retry.

Claude Code never called production.deploy. It did not need to. The allowed MCP action handed the work to a system with more authority.

The review packet said the agent respected its permissions. The incident record said production moved. Both statements were true, which meant the permission model was wrong.

All systems, identifiers, and records below are fictional examples.

Transitive effect boundary for an allowed Claude Code MCP tool

A tool list only shows the first hop

A permission such as ci.job.retry answers which endpoint the agent may call. It says nothing about the events, queues, service accounts, and automatic workflows behind that endpoint.

That gap is easy to miss during review. Retry, preview, validate, and refresh sound narrow. In a connected system, any of them can trigger a write elsewhere.

I would add an effect declaration to every state-changing MCP method:

method: ci.job.retry
classification: state_change
possible_effects:
  direct:
    - ci.execution.create
  delegated:
    - artifact.publish
    - release.event.emit
    - production.deploy
maximum_effect_scope: production

Now the contradiction is visible. The run denies production deployment, but one allowed method has a path to that exact effect.

This is broader than CI. A cache refresh may start a migration. A configuration check may fire a webhook. A report request may fan out into paid jobs. The safe label on the method does not reduce the authority of the systems it can wake.

Build the effect graph before granting access

For each allowed method, follow the chain until it stops or reaches a more sensitive trust zone:

Claude Code
  -> ci.job.retry
  -> build runner
  -> artifact registry
  -> release event
  -> deployment controller
  -> production

Each edge needs four facts:

effect_edge:
  trigger: artifact.published
  downstream_principal: release-service-example
  target_environment: production
  automatic: true

Do not draw this once and leave it in an architecture folder. Generate or validate it from CI definitions, event subscriptions, gateway policy, and service account permissions. If one edge cannot be resolved, record unknown_effect_path. Unknown must not quietly become allowed.

This effect graph belongs beside the MCP tool contract. The contract says what the tool accepts and returns. The graph says what the surrounding system may cause after the call returns.

Resolve authority against the largest known effect

Before execution, compare the requested scope with the closure of all known effects:

capability_resolution:
  requested_method: ci.job.retry
  requested_scope: test
  direct_effect_scope: ci
  transitive_effect_scope: production
  crossed_trust_zones:
    - artifact_registry
    - release_control
    - production
  policy_result: deny
  reason: transitive_scope_exceeds_requested_scope

The method is safe for this run only when every known effect fits inside the approved scope. If the path reaches production, the run needs production authority even when Claude Code cannot see a deploy button.

This check should happen before the run and again when the sensitive effect is about to commit. Workflows change. Permissions expire. A plan approved against yesterday’s effect graph should not inherit authority after a new subscriber is attached to the event.

Break the chain where the consequence begins

The clean fix is an enforcement gate at the deployment controller.

Let the CI retry build and test an artifact. Do not let it emit an automatically deployable release instruction. Promotion becomes a separate operation with a fresh, short-lived capability. Bind that capability to the artifact digest, target environment, policy version, approver, and expiry.

release_capability:
  artifact_digest: sha256:example-artifact
  target_environment: production
  policy_version: release-policy-example-12
  approved_by: reviewer-example-7
  expires_at: 2026-09-11T14:15:00Z
  single_use: true

The deployment controller validates this record immediately before changing production. The upstream retry cannot lend its approval to a downstream action with a different consequence.

A sentence in the prompt cannot provide this control. The system that owns the effect must enforce it.

Keep the root operation visible downstream

An audit trail often loses the agent after the first MCP response. The build runner, registry, and release service then appear to act independently.

Carry one root operation ID through every delegated event:

delegated_effect_trace:
  root_operation_id: op-example-911
  root_principal: claude-worker-example-14
  requested_method: ci.job.retry
  downstream_events:
    - sequence: 1
      system: ci
      effect: execution_created
    - sequence: 2
      system: artifact_registry
      effect: artifact_published
    - sequence: 3
      system: release_control
      effect: deployment_requested
    - sequence: 4
      system: production
      effect: deployment_blocked
      reason: missing_release_capability
  final_state: contained

The actor changes at each hop, but the cause does not disappear. That link lets operators measure the agent’s full blast radius, including downstream cost.

It also extends the causally ordered audit trail. Sequence and parent event links show how an allowed retry became a blocked deployment request. Wall-clock timestamps alone cannot do that reliably across services.

Test allowed calls that should end in denial

A basic permission eval calls production.deploy and expects a denial. That test would pass while the indirect path remained open.

The useful fixtures begin with allowed actions:

  1. Retry a CI job that publishes a releasable artifact. The deployment gate must stop it.
  2. Revoke the root capability after the build event. Every queued consumer must recheck authority.
  3. Add an unregistered webhook subscriber. Resolution must return unknown_effect_path.
  4. Trigger two cheap retries that fan out into paid work. The shared run budget must count the fan-out.
  5. Remove the parent operation ID from one event. The review verdict must become unproven.

Test the actual workflow in an isolated environment. Mocking the first MCP response will prove the endpoint contract while skipping the system that caused the incident.

Rollback must cover every committed effect

A code revert would not repair this event chain. The workflow created a CI execution, published an artifact, emitted a release event, and changed production state.

Write recovery against the effect ledger:

rollback_chain:
  root_operation_id: op-example-911
  effects:
    - effect: ci_execution
      recovery: cancel_or_archive
    - effect: artifact_publish
      recovery: quarantine_digest
    - effect: release_event
      recovery: invalidate_event
    - effect: production_deploy
      recovery: restore_previous_artifact_and_verify
  first_irreversible_effect: production_deploy
  rollback_rehearsal_status: passed

If a downstream effect has no tested recovery, the upstream tool is not low risk. The rollback rehearsal should use the same artifact and effect graph that the approval covers.

My review rule is blunt: approve the largest effect an MCP method can cause, not the name printed on its button. If the path is unknown, keep the method outside the agent boundary or stop it with a separate gate.

Claude Code: Building Production Agents That Actually Scale covers the operating controls needed to bound MCP effects, separate approval from execution, test denial paths, and produce review evidence for the whole operation.