You ask Claude Code to summarise a ticket. The task needs a read. The credential behind its MCP tool can also close the ticket.
That mismatch does not prove the agent can close anything. A read-only wrapper may block the write path. But it does mean someone needs to identify where read-only is enforced, rather than assuming the prompt settled it.
Updated 8 October 2026 with an executable local boundary test. The fixture uses synthetic data and no network. It teaches a control design; it does not validate your MCP server or a particular Claude Code permission mode.
Review happens after the agent has already acted inside the boundary you gave it. If the boundary was too wide, the reviewer is no longer checking a neat patch. They are investigating a larger system action after the fact.
That is why I treat Claude Code permissions as part of the product design, not setup plumbing.
Human review cannot repair every boundary mistake
Review is still needed. I am not arguing for hands-off agents near production code.
The problem is pretending review can compensate for a vague operating envelope.
If Claude Code was allowed to edit the whole repo, call broad tools, inspect unrelated files, touch configuration, and retry until something went green, the reviewer inherits all of that ambiguity. They have to work out what the agent meant, what it ignored, whether the task widened, and whether some unrelated change is now hiding in the diff.
That is a bad trade. The agent saved time during the run, then pushed the cost into review.
I have seen the same shape in financial-services engineering without agents: weak change boundaries create expensive reviews. Coding agents make the loop faster, but they do not make the control problem disappear.
A better rule is blunt:
If a reviewer cannot understand the boundary, the run was too broad.
Permissions decide the work before the prompt does
A prompt can ask for discipline. Permissions enforce some of it.
Before a run starts, the team should know:
- which files and directories are in scope
- which areas are read-only context
- which commands are allowed without approval
- which commands always require a human
- which MCP servers are available for this task
- whether the agent can touch auth, billing, infra, migrations, secrets, or production data
- what evidence the agent must leave before the patch is reviewed
This is not about locking Claude Code down forever. That would just teach people to work around the process.
The point is to make autonomy conditional. Start narrow. Watch what the run produces. Widen only when the evidence is boring in the best possible way: small diff, named commands, useful tests, clear risk note, clean rollback path.
If the evidence is messy, shrink the boundary.
MCP turns permissions into capability design
MCP is where this gets serious.
A coding agent with file access can make a bad patch. A coding agent with broad tool access can make a bad patch and change the world around the patch.
That does not make MCP bad. It makes MCP a capability boundary.
I would avoid a generic “MCP allowed” setting for production work. Name the capability instead:
| Capability | Safer starting point | Needs stronger approval |
|---|---|---|
| issue tracker | read the ticket and comments | create, close, or reassign tickets |
| docs/search | read approved docs | publish docs or edit runbooks |
| CI/CD | read build status | trigger deploys or change pipelines |
| database | query sanitized dev data | touch production data |
| cloud tools | inspect non-prod config | mutate infrastructure |
The question is not “Can Claude Code use this tool?”
The better question is: “Should this task have this capability today?”
That one word, today, matters. A migration task, a docs cleanup, a test repair, and an incident follow-up should not all get the same tool budget.
Test the read and the write through the same boundary
Tool exposure and credential scope are different controls. A gateway can refuse to expose ticket.close while holding a credential that permits it. Conversely, a gateway can expose that operation while the target refuses it because the credential only permits reads.
The second case is useful protection if someone later adds an alternate write tool. The first case is still a real boundary for the path it controls. Neither justifies claiming that every route is safe.
Download the read-only boundary fixture, inspect it, then run it with Python 3:
python3 read-only-credential-boundary.py
The fixture models an exposed-tool gateway and a target that checks credential scope before changing a synthetic ticket. Its observer belongs to the test harness, not the agent. All six regression tests passed when run for this update. Some tests deliberately reproduce unsafe behavior and pass because they detect it.
| Exposed operations | Credential scope | Observed write result | Target state |
|---|---|---|---|
| Read only | Read and close | Gateway denies | Unchanged |
| Read and close | Read and close | Close succeeds | Changed: unsafe for this task |
| Read and close | Read only | Target denies | Unchanged |
| Read only | Read only | Gateway denies | Unchanged |
Each row starts with a successful read of the synthetic ticket. That positive control matters: an unavailable endpoint can make every operation fail without proving anything about permission enforcement. A fifth test checks that an offline target raises a connection failure rather than recording a permission pass.
The sixth test catches a more awkward bug. A broken adapter changes the ticket and then returns a denial. The probe rejects that result because its independent state check finds the effect. A reassuring error message cannot undo a write.
In this fixture, the observer reads the in-memory target directly. In your integration test, use a separate authorized observer against a disposable resource and check the actual target. Do not give the agent broader access just so it can inspect or undo the test. If the provider applies changes asynchronously, observe the documented completion window; an immediate unchanged read is not enough. Record an unresolved result as unknown, not safe.
The example has no MCP transport, identity provider, concurrent writers or delayed effects. It cannot establish that your deployed credential has the scope you intended. Its purpose is to make the acceptance test concrete before you wire it into the real enforcement path.
Put the denial and its effect check in the review packet
Use this worksheet for the integration test. These are blank fields to fill from evidence, not Claude Code configuration:
task: summarise_synthetic_ticket
environment: disposable_test_target
resource_id: REPLACE_WITH_SYNTHETIC_ID
exposed_operations: []
effective_credential_scope: [] # scope names only, never token values
enforcing_component: NOT_CHECKED
permitted_read_result: NOT_RUN
forbidden_write_result: NOT_RUN
structured_denial_code: NOT_OBSERVED
independent_observer: NOT_ASSIGNED
target_state_before_ref: NOT_CAPTURED
target_state_after_ref: NOT_CAPTURED
effect_completion_window: NOT_VERIFIED
verdict: UNKNOWN
review_owner: NOT_ASSIGNED
Keep the target observations in a restricted test record and link them by reference. Avoid copying credentials or real ticket content into a review packet. A permission change should trigger the paired test again, including any newly exposed alternate path. For execution already in progress, permission scope is only part of the problem: stopping the agent does not necessarily cancel queued work.
Use a permission ladder
I like permission ladders because they make access feel earned instead of granted by default.
A simple version looks like this:
Level 0: read-only exploration
Level 1: patch in one narrow area
Level 2: patch plus tests in a known subsystem
Level 3: cross-cutting change with explicit human checkpoints
Level 4: autonomous batch work only after strong evals and rollback discipline
Most teams do not need to start at level three. They start there because the demo was exciting, the tool feels productive, and everyone wants to see what happens.
That is a poor reason to widen blast radius.
Move up the ladder when repeated runs leave evidence you would trust during an incident review. Move down when the run drifts, skips obvious checks, changes surprise files, or hides risk behind a confident summary.
The ladder is not bureaucracy. It is a memory aid for the team when the agent feels almost smart enough to trust.
Almost is where a lot of production trouble lives.
The run record is what changes the next boundary
Every useful Claude Code workflow should leave a run record.
For permission decisions, the record should include:
- the original task boundary
- files touched and why
- commands run
- checks passed
- checks skipped, with reasons
- MCP tools called
- assumptions and risks
- rollback notes
- anything the agent wanted to do but was not allowed to do
That last item is underrated. If the agent repeatedly asks for a tool or file outside the current boundary, the team can decide whether the boundary is too tight or the task is too broad. Either answer is useful.
Without that record, permission decisions become vibes. Someone remembers that the last run “went fine” and the next run gets more room.
That is how teams accidentally promote a demo habit into an operating model.
A practical policy for team adoption
Here is the policy I would start with for a team using Claude Code on real repositories:
Default: narrow write scope.
Default: MCP tools off unless the task names them.
Default: auth, billing, infra, secrets, migrations, and production data require human approval.
Default: no widening access mid-run without recording why.
Default: no merge without a run record and rollback note.
That policy will annoy people a little. Good. The annoyance is small compared with the cost of a broad agent run that nobody can explain two days later.
You can relax the defaults when the workflow earns it. Not because the model improved. Not because a demo went well. Because your own run records show the agent can operate inside a boundary and leave enough evidence for a human to make a good decision.
Keep the paired test with the run record so the next reviewer can see what the boundary actually refused.
Review should start with the boundary
Claude Code is powerful because it can hold context, edit code, run commands, and work through tasks that used to take a lot of developer attention.
That power is exactly why permissions matter.
A good team workflow should make the boring parts visible: the boundary, the tools, the checks, the risks, and the rollback path. When those are visible, reviewers can spend less time reconstructing the run and more time judging the change.
For the operating loop around tool permissions, evals and human review, see Claude Code: Building Production Agents That Actually Scale, available on Kindle. The landing page explains the available editions; this fixture is a public teaching example, not a claimed book excerpt.