Managed fleet
Anyone who can edit their own settings file can remove the hook. Managed settings is the answer, and it changes what your ledger is worth.
Installing on a managed fleet
This is the npx road at scale. Anyone who can edit ~/.claude/settings.json can
remove the hook, and that is the real bypass rather than a permission mode.
The SDK road does not have this problem in the same shape. The check is a line in your harness, so removing it is a code change that goes through review, and the gate is somewhere the developer does not administer.
Installed by a developer, Coco binds the agent and not the developer. Installed by an organisation through managed settings, it binds both, and a developer cannot opt out. Which of the two you are running decides what your ledger is worth to anyone outside your team.
What to set
Put the hook in managed settings, which a user cannot override, and set
allowManagedHooksOnly so only the organisation's hooks load and a user's own are
blocked.
| Setting | Why |
|---|---|
| The hook in managed settings | A user cannot override or remove it |
allowManagedHooksOnly | Only the organisation's hooks load |
disableBypassPermissionsMode | Closes the mode, though it is not the hole |
disableAutoMode | The same |
The last two are worth setting and they are not what stops a bypass. On the evidence in Can the agent get past it, neither of those modes defeats the gate, because a PreToolUse hook runs before the permission prompt those modes skip.
The file itself
Managed settings live where a user cannot write. On Linux that is
/etc/claude-code/managed-settings.json, and on macOS it is
/Library/Application Support/ClaudeCode/managed-settings.json. The entry is the
same shape the installer writes into a user's own settings, plus the two policy
keys.
{
"allowManagedHooksOnly": true,
"disableBypassPermissionsMode": "disable",
"hooks": {
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python3",
"args": ["/opt/coco/gate/coco_gate.py"],
"timeout": 30
}
]
}
]
}
}Put the gate itself somewhere a user cannot write either, /opt/coco in this
example, because a hook that points at a user-writable script is a hook the user
can rewrite. Ship the contracts to the same place through whatever configuration
management already reaches those machines, and set COCO_HOME for the fleet so
the gate reads them from there.
Distributing the contracts
The hook points at ~/.coco/gate, which the installer populates. On a fleet the
contracts want to arrive the same way every other policy artefact does, through
whatever configuration management already reaches those machines.
Two properties make that straightforward. The installer never clobbers an existing contract set or mandate, so pushing a set and then running the installer leaves your set in place. And the installer refuses to write the hook at all if the contracts do not compile, so a broken policy push fails loudly on the machine rather than silently at the first tool call.
Pin the contract version you rolled out. A receipt names the mandate it was decided under, so an auditor can tie a decision to the contract that governed it, and that only works if you know which version was where.
What this still does not cover
A session on a machine where Coco is not installed is not governed. That is an argument for deploying it as policy rather than as a developer preference, and it is the reason this page exists.
The gate sees tool calls. It does not read what the model says, and it does not supervise what a permitted call does once it is running.
Next
- What it does not stop for the full threat model
- Can the agent get past it for the measured result