Deploy zn-gate to a whole team
Put zn-gate's hooks on every machine through each agent's managed configuration: Claude Code managed settings, Cursor enterprise hooks and Codex requirements.toml, and how to check a machine.
This page is for the person who sets up laptops for a team. Instead of asking each developer to run zn hooks install, you place one configuration file per agent in a system folder, with your own tool for that (device management, Ansible, an image, a login script). The agent then runs zn-gate's hooks for everyone, and for Claude Code and Codex you can also stop users and projects from switching them off.
zn-gate does not deploy these files for you: it is a local guardrail, not an MDM (see Team overview). Everything below works by hand today.
Facts about each agent come from that agent's own documentation, read on 2026-10-19; check it again before a large rollout, because these settings change. The hook commands are the ones zn hooks install writes for a project install in zn-gate 1.12.0 or later.
Before you start
- Install zn-gate on every machine, the same version everywhere. It needs Node.js 18 or later:
npm install -g zn-gate. Do this first. A hook that cannot run is not harmless: Claude Code and Codex let the tool call go ahead unchecked, and Cursor blocks file reads and prompts when a hook hasfailClosedset. - Make sure the agent can find
zn. The commands below callzn hook <agent>, soznmust be on the PATH the agent runs with. Desktop apps do not always get the PATH of your terminal. If you are not sure, write the full path instead (command -v znon macOS and Linux,where znon Windows), for example/usr/local/bin/zn hook claude-code. - Try it on one machine, check it (see "Check a machine" below), then roll it out.
Claude Code
Claude Code reads its managed settings from a system folder:
| System | Folder |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/ |
| Linux and WSL | /etc/claude-code/ |
| Windows | C:\Program Files\ClaudeCode\ |
The folder holds managed-settings.json, an optional managed-settings.d/ folder of *.json files, and managed-mcp.json. Claude Code merges managed-settings.json first and then the files in managed-settings.d/ in alphabetical order, and it lists hooks from every file together. That lets you keep zn-gate's hooks in their own file, for example managed-settings.d/50-zn-gate.json, next to the policy other teams own.
Hooks and allowManagedHooksOnly
{
"allowManagedHooksOnly": true,
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|PowerShell|Monitor|Read|Grep|LSP|Edit|MultiEdit|Write|NotebookEdit|WebFetch",
"hooks": [{ "type": "command", "command": "zn hook claude-code", "timeout": 15 }]
}
],
"PostToolUse": [
{
"matcher": "^(Bash|PowerShell|Monitor|Read|Grep|LSP|WebFetch|ReadMcpResourceTool)$|^mcp__.*",
"hooks": [{ "type": "command", "command": "zn hook claude-code", "timeout": 15 }]
}
],
"UserPromptSubmit": [
{
"hooks": [{ "type": "command", "command": "zn hook claude-code", "timeout": 15, "onFailure": "block" }]
}
]
}
}
"onFailure": "block" makes Claude Code block a prompt when the hook cannot start. It needs Claude Code 2.1.295 or later; older versions ignore the field.
From zn-gate 1.13.0, zn-gate also checks what a typed skill or command expands into. Add this entry under hooks for it: "UserPromptExpansion": [{ "hooks": [{ "type": "command", "command": "zn hook claude-code", "timeout": 15, "onFailure": "block" }] }].
What allowManagedHooksOnly does. With true, Claude Code runs only the hooks that come from managed settings (plus hooks that the Agent SDK registers and hooks of plugins your managed settings force-enable). It blocks everything else: the hooks in a user's ~/.claude/settings.json, in a project's .claude/settings.json and .claude/settings.local.json, in other plugins and in agent frontmatter. It has side effects: the /goal command stops working, and a custom status line must come from managed settings. So decide on purpose, and tell your developers.
The setting protects zn-gate only if zn-gate's hooks are in the managed settings. If you set allowManagedHooksOnly without them, the hooks that a developer installs with zn hooks install claude-code or the zn-gate plugin stop running, and Claude Code shows no message. zn-gate tells the developer in zn hooks install claude-code and zn-gate status, and stays quiet when the managed files hold zn-gate's hooks.
Also keep in mind:
disableAllHooks: truein managed settings switches off every hook, yours included. Leave it unset.- If you deliver policy in more than one way (server-managed settings, device management, the Windows registry and files), Claude Code by default uses the highest-ranked source that sets a policy and ignores the others, so a file can be ignored. Put the zn-gate hooks in the source Claude Code uses on that machine.
/statusshows it on theSetting sourcesline. zn-gate reads only the files.
MCP servers (optional)
The hooks are what protect the agent. If you also want developers to use the zn-gate secrets MCP server (it lets the agent use a secret kept in zn's vault without seeing it), the managed MCP rules decide whether it can start. Be careful: a managed-mcp.json file takes exclusive control. Once it exists, Claude Code loads only the servers in that file (and a few built-in ones), and users cannot add any other server. List every server your team needs, not only zn-gate's. With an empty mcpServers object, no server loads at all.
managed-mcp.json uses the same format as a project .mcp.json:
{
"mcpServers": {
"secrets": {
"type": "stdio",
"command": "zn",
"args": ["secrets-mcp", "--agent", "claude-code"]
}
}
}
If you do not want exclusive control, use the allowlists allowedMcpServers and allowManagedMcpServersOnly in managed settings instead, and add the secrets server to the allowlist. See Claude Code's Managed MCP configuration. zn-gate status tells a developer when a managed file or list stops the secrets server.
Cursor
Cursor reads enterprise hooks from a system hooks.json:
| System | File |
|---|---|
| macOS | /Library/Application Support/Cursor/hooks.json |
| Linux and WSL | /etc/cursor/hooks.json |
| Windows | C:\ProgramData\Cursor\hooks.json |
{
"version": 1,
"hooks": {
"beforeMCPExecution": [{ "command": "zn hook cursor", "timeout": 30 }],
"beforeReadFile": [{ "command": "zn hook cursor", "timeout": 30, "failClosed": true }],
"beforeShellExecution": [{ "command": "zn hook cursor", "timeout": 30 }],
"beforeSubmitPrompt": [{ "command": "zn hook cursor", "timeout": 30, "failClosed": true }],
"postToolUse": [{ "command": "zn hook cursor", "timeout": 30 }],
"preToolUse": [{ "command": "zn hook cursor", "timeout": 30, "matcher": "Grep|Write|Delete" }]
}
}
This is the same set of hooks as zn hooks install cursor, with failClosed on the two hooks that read files and prompts. With failClosed: true, a crash, a timeout or no answer from the hook blocks the action instead of letting it through. That is why zn-gate has to be installed first: on a machine without zn, Cursor would block file reads and prompts.
What Cursor does with several sources: it runs the hooks of every hooks.json (enterprise, team, project and user), enterprise first in priority, and a deny from any of them wins. So a developer's own hooks still run next to yours. Cursor's documentation (read 2026-10-19) describes no setting that makes only enterprise hooks run, so zn-gate cannot be made mandatory in Cursor the way it can in Claude Code and Codex. Cursor's enterprise plan can also distribute hooks from its web dashboard; this page covers the file only.
zn-gate does not cover the Cursor command line (cursor-agent).
Codex CLI
Codex reads admin requirements from a system requirements.toml:
| System | File |
|---|---|
| macOS and Linux | /etc/codex/requirements.toml |
| Windows | %ProgramData%\OpenAI\Codex\requirements.toml |
On macOS, device management can also deliver the same content as requirements_toml_base64 in the com.openai.codex domain. See OpenAI's Managed configuration.
allow_managed_hooks_only = true
[features]
hooks = true
[hooks]
managed_dir = "/opt/example-managed-hooks"
windows_managed_dir = 'C:\ProgramData\ExampleCorp\managed-hooks'
[[hooks.PreToolUse]]
matcher = "^(Bash|apply_patch)$"
[[hooks.PreToolUse.hooks]]
type = "command"
command = "zn hook codex"
command_windows = "zn hook codex"
timeout = 30
statusMessage = "zn: checking for secrets"
[[hooks.PostToolUse]]
matcher = ".*"
[[hooks.PostToolUse.hooks]]
type = "command"
command = "zn hook codex"
command_windows = "zn hook codex"
timeout = 30
statusMessage = "zn: checking for secrets"
[[hooks.UserPromptSubmit]]
[[hooks.UserPromptSubmit.hooks]]
type = "command"
command = "zn hook codex"
command_windows = "zn hook codex"
timeout = 30
statusMessage = "zn: checking for secrets"
What the keys do, from Codex's documentation:
allow_managed_hooks_only = truemakes Codex skip hooks from users, projects, sessions and plugins, and load only managed hooks.[features] hooks = truekeeps hooks on even for a user who turned them off in their ownconfig.toml.managed_dirandwindows_managed_dirmust be absolute paths of folders that exist, or Codex does not load the managed hooks. zn-gate needs no script in them, so an empty folder that you create is enough.- Managed hooks count as trusted by policy: unlike hooks a developer installs, they do not need the review in
/hooks, and the user cannot turn them off there.
If the hook cannot start, Codex lets the call (and the prompt) go ahead, so the same rule applies: install zn-gate first.
Check a machine
zn-gate check at version 1.12.0 reads each user's own setup, not the managed files. On a machine where zn-gate is deployed only through managed configuration, it says "zn is not set up for this agent", even when the managed hooks work. Do not treat that line as a failure of your rollout. Use these checks instead.
-
Ask the agent which settings it uses.
- Claude Code: run
/statusand read theSetting sourcesline. It must list the enterprise managed settings (for files, "file").claude doctorlists what Claude Code dropped. - Cursor: open the Hooks tab in Customize, and the Hooks output channel to see hooks that ran and any error.
- Codex: run
/hooks. Managed hooks are marked as managed.
- Claude Code: run
-
Run
zn-gate statuson a Claude Code machine. It reports, in one line, a managed setting that stops zn-gate's hooks or itssecretsserver (disableAllHooks,allowManagedHooksOnlywithout zn-gate's hooks,managed-mcp.jsonwithout the server, an MCP allowlist or denylist). No line means it found none in the files; it cannot see policy that comes from server-managed settings, device management or the registry. -
Test the hook command itself. On macOS or Linux, in an empty folder, make a test
.envfile with a fake key and ask the hook to read it, as the agent does:printf 'ZN_CHECK_EXAMPLE_KEY=zn-check-fake-value-not-a-real-key\n' > .env printf '{"hook_event_name":"PreToolUse","session_id":"test","cwd":"%s","tool_name":"Read","tool_input":{"file_path":"%s/.env"}}' "$PWD" "$PWD" | zn hook claude-codeThe answer must contain
"permissionDecision":"deny". For an ordinary file such asREADME.txt, the answer must not. The same test for Codex is aBashcall with"command":"cat .env"piped tozn hook codex; for Cursor it is{"hook_event_name":"beforeReadFile","cwd":"…","file_path":"…/.env","content":"x","workspace_roots":["…"]}piped tozn hook cursor, which answers"permission":"deny". This proves that the command you wrote starts and that zn's rules block the file. It does not prove that the agent calls it: steps 1 and 2 do that. -
See it end to end. In a throwaway folder that holds only the fake
.envabove, ask the agent to read it. It must be refused with a message that starts withzn:.
Keep it working
- Update every machine together. Install the same zn-gate version everywhere, and change the managed files only to match what that version needs; the changelog says when hook entries change (for example, 1.13.0 adds an entry for Claude Code).
- Join the organization. Managed hooks run the checks; the organization policy and audit trail need each machine to join with
zn team join. See Join a machine and Organization policy. - To remove zn-gate, delete the entries from the managed files;
zn hooks uninstallonly removes what it wrote itself.
What this page does not cover
OpenCode, and agents zn-gate does not support yet (see Docs for AI agents). Windows was not tested for the Cursor and Codex files. Tell us at support@usezn.com if something here does not match what you see.