Part 5: How to govern Claude Code by separating guidance from enforcement

How to govern Claude Code by placing every rule that must hold in the enforcement layer: hooks, permission rules, managed settings and protected configuration, with guidance kept short and scoped.

Claude Code combines a large language model (LLM) with tools for reading files, editing code and running commands. Some of the rules that govern it are guidance: instructions the model reads and may or may not follow. Others are enforcement: controls that Claude Code or the operating system applies to a tool call or a file, whatever the model decides. Govern Claude Code by placing every rule that must hold in the enforcement layer, keeping guidance short and scoped, protecting the files that define the controls and recording every enforced refusal. Anthropic: How Claude Code works

This part explains the distinction, the weaknesses to look for in an existing configuration, the controls to adopt and how to verify each one. It is written for organisations that run Claude Code in long sessions with little human review of individual actions, and for the engineers, technology risk owners and AI governance leads who maintain or assure such configurations.

Terms used in this part

Term Meaning in this part
Guidance Anything the model reads and then chooses to act on, such as CLAUDE.md files, rules files and injected reminders.
Enforcement A control that decides whether a tool call proceeds, or that protects a resource, regardless of what the model decides.
Hook Code that Claude Code invokes at a defined event, such as a proposed tool call; its result can allow or refuse the action.
CLAUDE.md A Markdown file through which Claude Code receives user or project instructions.
Shared configuration The files at user level that apply to every project: the user CLAUDE.md, the decision record, settings, hooks, agent definitions, commands and templates.
Configuration owner The person or role accountable for the shared configuration and authorised to change it.
Decision record A file that records each configuration decision with its status, reason and consequences.
Auto mode A permission mode in which a classifier, rather than a person, approves or refuses most tool calls.
Managed settings Settings that an organisation deploys centrally and that user, project and local settings cannot override.
Main model The model that runs the main session, makes the decisions and judges delegated work.
Subagent A delegated agent given a task and its own working context.
Less costly agent A subagent that runs on a smaller, less expensive model and executes bounded jobs.
Brief The written instructions given to a delegated agent.
Compaction Condensing accumulated conversation into a summary so that work can continue with a smaller working context.
Breach log A log file to which each enforcement hook adds one line whenever it refuses an action. An entry records a refused attempt, not a successful breach.
Tripwire A check that compares protected files with a recorded baseline and flags any difference for review.
Immutable attribute A file system flag, supported by some operating systems, that prevents any change to a file until a privileged user removes it.
MCP Model Context Protocol, an interface for connecting applications with tools and contextual resources.
Git The version control system used to record and inspect file changes.
SSH Secure Shell, a protocol commonly used for remote access; its key and configuration files are among the resources a guard should protect.

Why guidance and enforcement must be kept apart

The documentation draws the line. Settings rules are enforced by the client regardless of what the model decides, whereas CLAUDE.md instructions shape behaviour but are not a hard enforcement layer (CLAUDE.md documentation). Permission rules are enforced by Claude Code, not by the model (permissions documentation). Everything in this part follows from taking that line seriously.

Where a classifier approves most tool calls, a person rarely sees an individual action before it runs. A configuration that grows up around this kind of use tends to mix the two layers, accumulating a great deal of guidance and very little enforcement. An instruction cannot stop an action that the model has decided to take, and an enforcement script written to allow the action when it crashes provides no protection at the moment it is needed.

The target configuration, meaning the arrangement this part recommends, separates the layers:

  • Guidance is reduced to a short universal rules file, with the rules for a particular project kept in that project and a decision record that can be read in one call.
  • Enforcement is a set of small hooks, each covering one class of irreversible or costly action, each written to deny on any internal error and each writing a line to the breach log that names the rule and the command.
  • The files that define the controls sit beyond the model's reach behind three independent layers, and the model proposes changes to them through a procedure that the configuration owner applies.

Plan for false positives and occasional hook crashes when the enforcement layer is first introduced, and diagnose each one from the breach log rather than from recollection, using the method in Part 6: How to diagnose Claude Code refusals and test governance hooks. A refusal produced by a hook leaves a log line and a tool result; a refusal produced by the model's reading of the rules leaves neither. Establishing which one occurred is the first step in every investigation.

Six weaknesses to look for in an existing configuration

A configuration that has grown without review often shows the six weaknesses below, none of which suits long, largely automated use. Use the table as an assessment checklist; the subsections explain each weakness.

Weakness Risk What to check
Rules unrelated to the session are loaded into every session Lower adherence, conflicting instructions and exposure of project details The length of the user CLAUDE.md and of everything it imports, and any content that belongs to one project
The decision record cannot be read in one call The operative position cannot be established Whether an index locates every entry within a single bounded read
A guard allows the action if it crashes The only deterministic stop disappears on malformed input The error handling of every enforcement hook
Model rules match literally Launches by alias or by agent type escape the rule How agents are actually launched, compared with the deny rules
A product guard is mistaken for an organisation's rule Behaviour is attributed to the wrong owner The product defaults on which the configuration relies, and those it disables
Tool output and repeated rules enlarge the context Higher cost and weaker attention to the rules The composition of the context over a long session

Rules unrelated to the session loaded into every session

A long rules file imported into the user CLAUDE.md is loaded into every session; imported files load at launch, so importing a file does not reduce its cost. If much of the file concerns a single project, such as a particular host address, the document conventions of one engagement or the behaviour of specific accounts, every unrelated session carries it, and so does every subagent that inherits it. The documentation recommends keeping each CLAUDE.md under 200 lines because longer files reduce adherence, and states that where two instructions conflict, Claude may pick one arbitrarily (CLAUDE.md documentation). A convention that lets one rule override another by number invites exactly such conflicts.

A decision record that cannot be read in one call

A decision record that mixes configuration decisions with project decisions grows quickly. Where reads are capped, a long record returns only part of its contents per call; for example, a record of 1,600 lines may return about a fifth of its contents in one read. With nothing at the top to show where an entry sits, and with entries amended in several different ways, the operative position cannot be read from the page.

A guard that allows the action if it crashes

An enforcement script is sometimes written to allow the action if it crashes, on the reasoning that a guard which blocks work will be disabled. That reasoning holds while a person sees most tool calls. Under auto mode the script may be the only deterministic stop, and a malformed input would remove it.

Model rules that match literally

A permission deny list may use rules of the form Agent(model:...). The documentation states that these rules compare the value with the literal input, before any normalisation, and that a parameter the launch omits is never matched (permissions documentation). A rule that lists full model identifiers therefore blocks those identifiers and nothing else. It does not match a launch that names the model by alias, a short name that resolves to a current version, and it does not match a launch by agent type that leaves the model to the agent's definition. Such launches can account for most of the total.

A product guard mistaken for an organisation's rule

Before a background session edits files, Claude Code moves it into its own git worktree, a separate working copy of the repository, and then enforces that isolation (agent view documentation). This isolation guard for background sessions is easily mistaken for one of the organisation's own rules, although a documented setting, worktree.bgIsolation, turns it off. Record the product defaults on which the configuration relies and those it disables, so that each behaviour is attributed to the right owner.

Tool output and repeated rules that enlarge the context

Tool output accumulates quickly. To illustrate the scale, in a session of about 780,000 tokens, shell (Bash) results can account for 195,600 tokens and file reads for 168,900. A reminder that reprints every rule heading on each turn can add about 650 tokens per message. Every token admitted to the context is sent again on every later turn until compaction.

How guidance and enforcement behave differently

Guidance: instructions the model reads and may follow

Guidance is anything the model reads and then chooses to act on: the CLAUDE.md hierarchy, rules files it imports, reminders injected by hooks, the compaction summary and the model's own memory of what it refused earlier in the transcript. CLAUDE.md is delivered as a user message after the system prompt and is read again from disk after compaction; instructions given only in conversation can be lost at compaction (CLAUDE.md documentation).

Guidance costs tokens on every turn, because it sits in the context that is sent again with each request. A rules file of 1,500 tokens, for example, costs 1,500 tokens per turn at the cache read rate, the reduced price for input served from the prompt cache. A reminder injected on every turn costs its length multiplied by every turn that follows it.

Guidance degrades in four ways that enforcement does not:

  1. It degrades with the length of the file, which is why the documentation recommends keeping CLAUDE.md under 200 lines.
  2. It degrades with the length of the context and the distance between the rule and the current turn. This is a judgement, consistent with the documentation's note on adherence, and it is the most likely explanation when a long session appears to forget a constraint.
  3. It is lost or compressed at compaction, so a rule given only in conversation may not survive.
  4. It can be wrong in a way that enforcement cannot. The model's recollection of a rule, or of a refusal, is a fact about the transcript, not about the configuration. A session resumed after a hook has been corrected may, for example, decline a command that only reads, on the strength of a refusal it remembers from before the correction. That is a correct application of a standing instruction not to retry a refused action, applied to a refusal that no longer exists.

A refusal the model makes because it has read a rule is guidance even when a hook also enforces the same rule. The hook was never reached, the log shows nothing, and the behaviour would be the same if the hook were deleted. This is the most important distinction for anyone investigating why a command did not run.

Enforcement: controls applied to the tool call or the file

Enforcement acts on the tool call or on the resource. In Claude Code it takes four forms:

  • hooks, which run a script before or after a tool call and, when they run before it, can refuse it;
  • permission rules in settings, which allow, ask or deny by tool and pattern, and whose deny and ask rules still apply when a hook allows the call (permissions documentation);
  • managed settings, which no user, project or local setting can override (settings documentation);
  • file system controls outside Claude Code altogether, such as the immutable attribute.

Claude Code loads hook configuration when a session starts. The current hooks documentation adds that a file watcher normally picks up direct edits to settings files during a session, and advises a restart to force a reload when a change has not appeared. A session that has not picked up a change keeps the old behaviour, whatever the files on disk now say, so restart every session after changing hooks or settings and confirm the change with a forced test.

A hook blocks a tool call by exiting with code 2 or by printing a valid decision on standard output. By default, any exit code other than 0 or 2, or a failure to start, is treated as an error that does not block, and the tool call proceeds. This fact decides how a safety hook must be written: a hook that raises an exception on unexpected input allows the action unless it is written to deny on error. Claude Code v2.1.295 and later also let a command hook, or an HTTP hook that sends the event to a web endpoint, declare "onFailure": "block"; a failure to start, an exit code other than 0 or 2, a timeout or invalid output then blocks the action. The hooks reference states the conditions, the exceptions and the minimum version.

Enforcement costs almost nothing in tokens. A hook's refusal reason enters the context as a tool result, a few hundred tokens once, and only when it fires. The cached prefix, the opening portion of the prompt that the provider can reuse from its cache, is untouched unless the hook itself injects context at the start of the conversation.

Enforcement leaves evidence. A refusal is a tool result in the transcript carrying the hook's own text and, where hooks are configured to log, a line in the breach log with the time, the session, the rule and the command. A model refusal leaves prose in the assistant turn and no tool call.

Where each layer lives, how it fails and what it records

Property Guidance Enforcement
Where it lives Markdown files and the transcript Settings, hook scripts, managed settings and file attributes
When it loads At session start, after compaction and continuously as the transcript grows At session start; restart after any change to be certain that it applies
What overrides it Anything later in the context, compaction and the model's own judgement Nothing the model does; managed settings override user settings, and nothing overrides managed settings
How it fails Silently, by being forgotten or outweighed Visibly if the hook denies on error; silently open if it does not
What it costs Its length, on every turn Its refusal text, once, when it fires
What it leaves Prose in the assistant turn A tool result and a breach log line
Instructions feed a model that can decline without requesting a tool or submit a tool call to a safety hook. The hook allows execution or returns a refusal and records its denial.
Figure 5.1. Two decision paths. Guidance can lead the model to decline before any tool request, leaving no record outside the transcript. A safety hook acts on a proposed tool call, allows or refuses it, and records each refusal in the breach log.

Controls to adopt

Adopt the controls below as a set. The table summarises each control, the layer it belongs to and how to verify it; the subsections give the detail. The guards and thresholds are examples: define one guard for each class of irreversible or costly action in your own environment, and set thresholds from your own measurements.

Control Layer How to verify
Scope rules: universal rules at user level, project rules in each project Guidance The line count of the user CLAUDE.md with its imports, and no project details at user level
Keep decision records indexed, with accepted entries never edited Record keeping The index fits in one read, and version history shows accepted entries unchanged
Install a small set of enforcement hooks before tool calls Enforcement Forced failure tests and the breach log
Add convenience hooks for coverage, reminders and session boundaries Guidance and detection Each notice appears when its event occurs
Make safety hooks fail closed Enforcement Crash tests inside the main function, at import time and with a hook that cannot start
Keep the shared configuration under owner control Enforcement and detection Refused model writes, a tripwire alert on a test change and a commit for every owner change
Pin models and define every delegated agent type Enforcement and configuration The agent launch log, deny tests by alias, identifier and agent type, and the resolved model at session start
Compact at task boundaries Guidance and record keeping The handover note is updated before each compaction
Delegate bounded work and check documents at release Guidance and procedure Agent reports compared with their logs, and a completed checklist for each released document
Enable extensions only where needed Configuration Enabled plugins and MCP servers compared with an approved list

Scope rules: universal rules at user level, project rules in each project

Partition rules by scope. Keep the user CLAUDE.md short and limited to rules that apply to every project; for example, about 48 lines, or roughly 1,500 tokens, can be enough. Keep rules that belong to one project in that project's own rules file, loaded only there, so that names, host addresses and other project details do not reach unrelated sessions or their subagents. Let project rules add to the user rules and never replace one. This removes conventions that override rules by number, and with them the documented risk that conflicting instructions are resolved arbitrarily.

To verify, count the lines that the user CLAUDE.md loads, including imports, against the documented recommendation of under 200 lines; search the files at user level for project names and host addresses; and confirm that no rule claims to override another.

Keep decision records indexed, with accepted entries never edited

Split the decision record. Keep configuration decisions in their own file, with an index of every entry and a fixed form: status, date and decider, decision, reason, rejected alternative, consequences and related entries. Never edit an accepted entry; record a later change by adding a status line to the old entry and writing a new one. Archive project decisions verbatim and give each project its own decision file.

To verify, confirm that the index can be read in a single bounded read, that every entry has each field, and that version history shows no change to an accepted entry other than an added status line.

Install a small set of enforcement hooks before tool calls

Install a small hook for each class of irreversible or costly action. Each hook should deny on any internal error and add a line to the breach log naming the rule and the command. The set below is an example; adapt the classes to the actions that your organisation cannot easily reverse or afford.

Guard What it refuses or requires Risk addressed
System guard Refuses manipulation of operating system services and writes inside the SSH key directory Changes to the host and to remote access credentials
Write guard Refuses model writes to the shared configuration, writes to project configuration during a session, and any write that replaces a whole file of more than 200 lines that the session has not read in full Tampering with the controls, and loss of content the model has not seen
Shell guard Routes test runs, project builds, diffs, bulk file work and batched git operations to a less costly agent; requires agents to redirect output to a log; bounds output; refuses commands other than reads on shared configuration paths Cost, growth of the context and tampering with the controls
Agent guard Requires every launched agent type to have a definition file, every explicit model to be a pinned alias or a listed identifier, every brief with its named inputs to stay under a size limit such as 30,000 tokens, and every launch to be logged Unreviewed agents, unintended models and uncontrolled cost

To verify, run the forced failure tests in Part 6 for each guard, and review the breach log weekly.

Add convenience hooks for coverage, reminders and session boundaries

Convenience hooks help the model and the operator but enforce nothing. Classify them as guidance or detection, not as controls that stop an action.

  • After a file read, a coverage hook states the lines delivered against the file's total and the ranges still unread.
  • After any edit to a project rules file, a hook shows a notice.
  • At intervals, for example every 120,000 tokens or 40 prompts, a hook appends a reminder of the routing and bounding rules at the end of the conversation, so that the cached prefix is untouched.
  • Before compaction, a hook snapshots the transcript and records any uncommitted work and the age of the handover note, the file that records what was requested, what was done and what remains unresolved.
  • At every session start, a hook injects the rule headings, the shared configuration policy, the handover note and the git state.
  • A status line, the line of session information that Claude Code can display, shows context use against the compaction trigger (the size at which Claude Code compacts automatically), the share of cache reads, the cost and a tripwire over the shared configuration.

To verify, trigger each event in a test session and confirm that the notice appears. Context that hooks add is summarised with the rest of the conversation at compaction (context window documentation), so treat every reminder as guidance that may not survive.

Make safety hooks fail closed and let convenience hooks fail open

Write every safety hook to deny on any internal error: wrap its main function so that any exception produces a deny decision. This follows from the exit code rule above, under which a crash that is not caught allows the action. Where the Claude Code version supports it, also declare "onFailure": "block" on safety hooks, so that a hook that cannot start, exits with an unexpected code, times out or returns invalid output blocks the action (hooks reference). Convenience hooks can fail open, because a missing coverage note is a nuisance and a blocked session is not.

To verify, run the crash tests in Part 6: an exception inside the main function, an exception at import time and a hook that cannot start.

Keep the shared configuration under owner control

Treat the shared configuration as owner controlled. Its files apply to every project, so only the configuration owner should change them. Protect them in three independent layers:

  • the immutable attribute on the two key documents, the user CLAUDE.md and the decision record, with the settings file excluded because Claude Code itself writes to it: it may reorder keys at startup, and the model command saves a model choice there (model configuration documentation);
  • hooks that refuse model writes, and commands other than reads, on shared configuration paths, with no override;
  • a tripwire in the status line.

Keep the configuration directory in a git repository whose ignore file excludes everything except the files listed.

Three parallel protection rows link immutable attributes to CLAUDE.md and the decision record, hooks to write requests on shared configuration paths, and a detection tripwire to monitored shared configuration files.
Figure 5.2. Three independent layers protect the shared configuration: immutable attributes on two documents, hooks that refuse model writes and commands other than reads on shared configuration paths, and a tripwire in the status line. Each layer has a different scope and purpose.

Let the model propose a change to the shared configuration through a command that warns, in capitals, that the change applies to every project, recommends the project's own file instead and writes a proposal. The configuration owner applies the proposal with a script that shows the diff, demands typed confirmation, makes a backup, removes and restores the immutable attribute, validates the JSON, commits the change and resets the tripwire baseline.

A model-side command warns about shared scope, recommends a project-local file and writes a proposal. The proposal passes to an owner-side script with diff review, typed confirmation, backup, attribute handling, JSON validation, a commit and tripwire-baseline reset.
Figure 5.3. Changes to the shared configuration cross a boundary that the owner controls. The model's command warns that the change applies to every project, recommends a project file instead and writes a proposal; the owner's script applies it with the checks listed.

Where the organisation manages the machines on which Claude Code runs, use managed settings as the outermost owner control, and deploy safety hooks, deny rules and the model allowlist there. No user, project or local setting can override managed settings. The permissions documentation also notes that a mod, an installed extension that can answer permission checks, can approve a call that a hook has blocked unless that hook is deployed in managed settings (permissions documentation).

To verify, attempt a model write to a protected file and confirm the refusal and the breach log line; make a test change through a route the hooks cannot see, such as a script, and confirm that the immutable attribute refuses it or the tripwire flags it; and confirm that every owner change appears as a commit.

Pin models and define every delegated agent type

Pin models. Resolve model aliases, which otherwise point to the provider's current recommended version, to named versions through environment variables such as ANTHROPIC_DEFAULT_OPUS_MODEL (model configuration documentation). Run subagents launched without a type on the less costly model. Derive agent types from definition files in the user or project agents directory (subagents documentation) rather than hard coding them. Define any coordinating agent, one that dispatches and supervises other agents, once, with an inherited model, and name its model at each launch. The documentation describes the model setting as an initial selection, not enforcement; a managed allowlist (availableModels) is the control that nothing in a session can override.

To verify, review the agent launch log for type and model, test the deny rules against launches by alias, by full identifier and by agent type, and check the resolved main model at the start of each session.

Compact at task boundaries and record state first

Compact at boundaries, meaning points at which a unit of work is complete. A boundary command can record test status from the log file, list uncommitted work, update the project's handover note and print the focus line for compaction, a short statement of what the summary should preserve; the automatic compaction trigger then serves only as a backstop. The reasoning is a judgement rather than documented behaviour: every turn sends the whole context again, so the cost of a long session is dominated by stale tool output, and a summary written from an oversized context full of distracting material loses the most.

To verify, check that the handover note was updated before each compaction and that recorded test status comes from a log file rather than from the model's own account.

Delegate bounded work and check documents at release

Let a less costly agent execute bounded, verifiable jobs under a brief that carries every decision. Keep with the main model the decisions, the drafting, the writing of test cases and their expected values, and the judgement of the agent's report against the log on disk rather than against the agent's prose. Have the main model write documents that it has composed itself. Delegating the write costs a brief carrying the whole text, a report and the overhead of starting the agent, which is more than writing directly; this cost argument is a judgement.

Check documents that leave the machine at the point of release, against a checklist covering a full bounded read, recipient and reference numbers, draft markers, every figure traced to its source, a hash and a register line, and an optional independent read by a separate agent. Leave the sending to a person.

To verify, sample delegated jobs and compare each report with its log, and confirm that every released document has a completed checklist entry.

Enable extensions only in the projects that need them

Turn plugins and MCP servers off by default at user scope and enable them in each project that needs them; disable claude.ai connectors and plugin sync where they are not needed. Reduce project settings files to permissions and hooks, dropping keys that belong to the shared configuration, and give the owner an inventory of project rules files rather than rewriting them with tooling.

To verify, compare the plugins and MCP servers enabled in each project with an approved list.

Vikram Mohan

A London based technology consultant who has a background in cybersecurity, risk, assurance and controls, covering their design, testing and reporting.

Vikram audited the controls of one of Europe's largest banks from within its Economic Crime and Cybersecurity Audit team, including third party risk reviews of its largest cloud and outsourcing partners, and reported to its audit committee. He has more than ten years of consulting, transformation and cybersecurity experience at Capgemini Invent, Deloitte, Accenture and Tata Consultancy Services, across the public sector, financial services and several other industries.

  • Frameworks: NIST Cybersecurity Framework, COSO Internal Control, GDPR and DPIA, UK operational resilience (PRA SS1/21, FCA PS21/3), DORA
  • BCS Chartered IT Professional (CITP); CISSP in progress
  • MBA, SP Jain School of Global Management; MSc Distributed Systems and Networks, University of Kent