Claude Code
This page is for an agent connecting a team's Claude Code to Zaun. It covers delivering managed settings to every developer, observing with telemetry, blocking with hooks, and the proof to show. If you are the person, you approve the settings and deploy them through claude.ai or your device-management tool; developers do not configure anything themselves.
| Goal | Use | Blocks? |
|---|---|---|
| See every prompt and tool call, alert, review sessions | Telemetry (OpenTelemetry export) | No, events arrive after the fact |
| Stop a prompt before it is sent or a tool before it runs | Hooks (HTTP hooks) | Yes |
| Both | One settings file with both blocks | Yes |
On Claude Enterprise, inference hooks can also block across claude.ai, Claude Cowork and Claude Code without laptop configuration; see Claude Cowork and choose a capture path. The console generates the settings document for your project; the blocks below show its shape.
Deliver settings to every developer#
Claude Code reads managed settings that developers cannot override. Pick one delivery path. If the organization already delivers managed settings, add the Zaun blocks to that same document rather than starting a second source.
Team and Enterprise plans: claude.ai managed settings
This is the path for most teams. An Owner or Primary Owner opens claude.ai Organization settings > Claude Code > Managed settings and pastes the generated settings document. Nothing is installed on laptops.
Because the document contains hooks and environment variables, Claude Code asks each developer to approve it, at their next start or within an hour, and again whenever those settings change. If a developer rejects the settings, Claude Code exits. Tell developers to expect the prompt.
Device management
Claude Code also reads managed settings from device management: the
com.anthropic.claudecode managed preferences domain on macOS, and the Settings value under
HKLM\SOFTWARE\Policies\ClaudeCode on Windows. The console generates a Jamf or Intune profile;
deploy it like any other configuration profile.
The managed settings file
On a single machine, or with your own deployment tooling, write the document to the managed settings file. Writing it needs administrator rights:
| System | Path |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json |
| Linux and WSL | /etc/claude-code/managed-settings.json |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json |
Observe with telemetry#
Claude Code exports OpenTelemetry logs when the managed settings carry this env block.
<INGEST_KEY> is an ingest key for your Claude Code telemetry source. It can only submit
events for one project, and it cannot scan or read results.
{
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_LOGS_EXPORTER": "otlp",
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://api.zaun.ai/v1/ingest/otlp",
"OTEL_EXPORTER_OTLP_HEADERS": "x-zaun-ingest-key=<INGEST_KEY>",
"OTEL_LOG_USER_PROMPTS": "1",
"OTEL_LOG_TOOL_DETAILS": "1",
"OTEL_LOG_ASSISTANT_RESPONSES": "1"
}
}
- Claude Code has no default protocol, so
OTEL_EXPORTER_OTLP_PROTOCOLmust be set. - The header uses
x-zaun-ingest-keybecause exporters split header strings on=and a value with a space, such asBearer <INGEST_KEY>, is read differently by different exporters. - The three
OTEL_LOG_*flags decide what text Zaun receives: prompt text, tool inputs (about 4,000 characters each) and the assistant's replies. Without them, Zaun sees that events happened but not what they said, and Hex v1.3 has nothing to assess. Tool output is not part of these logs. - Telemetry cannot block, and telemetry scans are billed like any other scan.
Block with hooks#
Claude Code sends each prompt and each tool call to Zaun before it runs, and Zaun answers with
allow or deny. Add this beside the env block:
{
"allowManagedHooksOnly": true,
"allowedHttpHookUrls": ["https://api.zaun.ai/v1/hooks/claude-code"],
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "http",
"url": "https://api.zaun.ai/v1/hooks/claude-code",
"headers": { "Authorization": "Bearer $ZAUN_HOOK_KEY" },
"allowedEnvVars": ["ZAUN_HOOK_KEY"],
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "http",
"url": "https://api.zaun.ai/v1/hooks/claude-code",
"headers": { "Authorization": "Bearer $ZAUN_HOOK_KEY" },
"allowedEnvVars": ["ZAUN_HOOK_KEY"],
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "http",
"url": "https://api.zaun.ai/v1/hooks/claude-code",
"headers": { "Authorization": "Bearer $ZAUN_HOOK_KEY" },
"allowedEnvVars": ["ZAUN_HOOK_KEY"],
"timeout": 10
}
]
}
]
}
}
- The hooks authenticate with
ZAUN_HOOK_KEY, a key with only thescan:writescope (<SCAN_KEY>), so the settings document itself holds no secret. Set the variable on each machine through device management. Setting it in the document'senvblock is not offered yet: whether Claude Code reads a header variable from that block is still being verified. Give the key a daily spending cap, since nobody watches what hooks spend. UserPromptSubmitchecks the prompt before it is sent. A blocked prompt is refused with Zaun's reason. It takes no matcher.PreToolUsechecks the tool name and its input before the tool runs. A blocked call is denied with Zaun's reason.- Hooks apply the same verdict as the API. Today a tool call is blocked by input checks such as the test string; a threat Hex v1.3 finds only in the agent's own tool calls normally raises an alert instead. A Redact control alerts in a hook, because a hook cannot rewrite the prompt. See what blocks today.
PostToolUserecords tool results but cannot block them: the tool has already run. A tool result is scanned and kept like a prompt: Zaun redacts it before storing it, and it shows in Violations and session retrieval. Tool results are often whole files or command output, and the redaction rules do not yet mask every secret format: a credential in a prefixed name such asDB_PASSWORD=, a value inside JSON-escaped text and anAuthorization: Basicheader stay readable to the project's Violations readers. A project that turns activity retention off keeps none.allowManagedHooksOnlyruns only hooks from managed settings, so developers' own hooks stop running.allowedHttpHookUrlskeeps HTTP hooks pointed at Zaun.
When Zaun cannot be reached
Zaun answers within 8 seconds; a scan that takes longer is answered with allow and a message
that Zaun did not answer in time. Hooks fail open by default: if Zaun does not answer within
the 10-second timeout, or answers with an error, the prompt or tool goes ahead. From Claude
Code 2.1.295, adding "onFailure": "block" to a hook fails closed instead, with one exception:
a tool check that times out still does not block. Fail closed stops developers' work whenever
Zaun or their network is unavailable, so decide it with the person.
What each reply does to the prompt or tool call:
| Zaun answers | Claude Code |
|---|---|
| 200 with a block | The prompt or call is refused with Zaun's reason. |
| 200 with allow and "Zaun did not answer in time" | The call goes ahead, even with "onFailure": "block": a late answer is a reply, not a failure. |
401 or 403: the key is wrong, revoked or lacks scan:write | A non-2xx reply. The call goes ahead, or is blocked under "onFailure": "block". |
| 413: the body is over 20 MiB | The same as above. |
| 422: the payload is not a valid hook call, or its tool input nests more than 128 levels | The same as above. |
| 429: the project's or organization's request allowance is spent | The same as above. |
402 key_daily_cap: the hook key reached its daily spending cap | The same as above, until the next UTC midnight. Zaun answers it the same way when it cannot read the key's spending counter and the key has used what Zaun admits without it. See Limits and errors. |
| 503: Zaun cannot scan right now | The same as above. |
A prompt or tool call over 131,072 characters is the exception to the error rows: Zaun refuses it with a block in a 200 reply, so a hook that fails open cannot skip the check. See Hook limits.
Individual developers#
A developer outside managed settings can install the Zaun plugin, which bundles the hooks:
claude plugin marketplace add zaun-ai/claude-plugin
claude plugin install zaun@zaun-ai
The plugin reads its key from ZAUN_HOOK_KEY. The key claim flow in
Set up with your agent writes it, so the key never passes through the
conversation.
The proof to show#
- Telemetry: the source's health report in the console shows the source receiving events, the first and last event times, and that prompt content is present. A connectivity check gives a short code to type into Claude Code; the report marks the check satisfied when that event arrives.
- Prompt hook: type the test string
ZAUN-TEST-VIOLATION-7f3a9cfrom Try it into Claude Code. The prompt is refused with Zaun's reason, and the event appears on Violations. - Tool hook: ask Claude Code to create a file whose content is
ZAUN-TEST-VIOLATION-followed directly by7f3a9c. The prompt does not contain the whole string, so it passes; the file write carries it and is denied before it runs.
Maintained with the API contract · Defaults may vary by deployment.