auspex: Claude Code Remote Settings and Managed Hooks (macOS)
Last updated: September 14, 2026
This page documents Claude Code behavior that can stop auspex from capturing Claude Code activity on managed (MDM) installations, along with the workarounds available today. It affects Claude Code only — auspex's capture from Cursor, Codex, GitHub Copilot and VS Code is unaffected.
Remote settings can override the managed hooks
auspex installs its hooks as a dedicated, auspex-owned file alongside your own managed settings, so it never modifies your configuration:
/Library/Application Support/ClaudeCode/managed-settings.d/auspex.json
Claude Code normally reads this drop-in and runs the hooks it defines. However, when your organization's Claude Code remote settings define their own hooks block, that block can take precedence over the managed drop-in — and auspex's hooks stop firing. The drop-in file is still on disk and still valid; Claude Code simply stops running the hooks it defines.
We have not fully characterised the conditions under which this happens, so treat the presence of an organization policy that defines hooks as a risk rather than a certainty. If you publish remote settings and Claude Code capture stops, work through the checks below.
The same behavior and the same workaround apply on Linux, where the drop-in lives at /etc/claude-code/managed-settings.d/auspex.json.
Symptoms
A device in this state looks healthy in every respect except that no Claude Code activity is ever recorded:
- Span receives heartbeats from the device, and traces from the device's other tools (Cursor, Codex, Copilot) still arrive — only Claude Code sessions are missing, while the same users are demonstrably using Claude Code.
auspex status --verbosereports everything green, including[OK] capture_wiring: all N tools wired. That check verifies the hook entries auspex wrote are present and correct on disk — which they are. It cannot see that Claude Code has decided not to run them.auspex status --jsonshows an"event_counter"underqueuethat never moves, with"depth": 0and no dead-lettered events. That is the tell: it means no hook has ever fired, rather than hooks having fired and failed to deliver.- Restarting the machine does not help, because nothing is broken on the device.
Confirming it
The most direct check is Claude Code's own debug log, which records every hook it runs and every settings source it loads. Have the user run this from a project directory:
claude --debug-file /tmp/cc-debug.txt -p "Run 'echo diag' with your Bash tool, then reply DONE."
grep -E "auspex|Watching for changes in setting files|Remote settings:" /tmp/cc-debug.txt
Read the result as follows:
- Lines like
[... /usr/local/bin/auspex hooks capture claude-code <Event>] completed with status 0— the hooks are running. This issue is not what you are seeing. - No
auspexlines at all — Claude Code is not running the drop-in's hooks. That is this issue. Remote settings: Applied new settings successfullyconfirms an organization policy is live on the device.Remote settings: No settings found (404)means there is none, which rules this issue out.- A
Watching for changes in setting files ...line that does not mention themanaged-settings.ddrop-in directory means Claude Code is not reading the drop-in at all.
You can corroborate with auspex's own counter. Note the value, have the user run Claude Code briefly so that it uses a tool, then check it again:
auspex status --json
queue.event_counter should increase by several events per tool call. If it does not move while the drop-in exists and capture_wiring passes, no hook has fired.
Workarounds
Either of the following restores Claude Code capture. Workaround 1 is the one we recommend — Workaround 2 is a last resort and carries the caveats set out below.
Workaround 1 — Copy the managed hooks into user settings via MDM (recommended)
Run a script from your MDM that reads the auspex-owned managed drop-in and merges its hooks into the user's own ~/.claude/settings.json, which is still honored.
The merge is additive and idempotent: existing hooks from other tools are left untouched, auspex hooks that are already present are not duplicated, and stale auspex hooks left over from an earlier version or a previous per-user install are removed. Entries are copied verbatim from the drop-in, so the per-hook "timeout" and the tool matchers auspex writes are preserved exactly.
Run it on a schedule so the user settings stay in step with the managed drop-in across auspex upgrades: when auspex updates its hook contract, the drop-in changes and the next scheduled run propagates the change automatically. Nothing has to be re-approved, re-published, or hand-edited.
Pros. Targetable at specific users or device groups rather than the whole organization. Raises no approval prompt for users. Tracks auspex's own hook definitions automatically, so it cannot drift out of date. Changes nothing about your Claude Code organization policy.
Cons. It requires MDM setup and ongoing scheduling, and it writes auspex configuration into user-level settings, which is not where a managed installation would normally place it. Users can edit or delete that file themselves, so the script needs to run repeatedly rather than once. It also conflicts with allowManagedHooksOnly — see below.
Note.
auspex hooks installcannot be used for this. On a centrally-managed device it deliberately refuses a user-tier placement, because on a device where the drop-in is working that would capture every event twice. The script below writes only the user settings file and leaves auspex's own placement untouched.
The script
Download merge-auspex-hooks.sh and run it from your MDM as the signed-in user. It needs no administrator rights, since it only writes inside the user's own home directory.
The script uses python3 to edit the settings JSON. That is present by default on macOS and on mainstream Linux distributions, so this is rarely something you need to arrange — but on a minimal image without it the script exits with a clear message and changes nothing.
curl -fsSL https://gist.githubusercontent.com/marianolg/efb9ec80a468eb07cf0e379762fc4a20/raw/merge-auspex-hooks.sh -o merge-auspex-hooks.sh
chmod +x merge-auspex-hooks.sh
./merge-auspex-hooks.sh
By default it reads the managed drop-in from /Library/Application Support/ClaudeCode/managed-settings.d/auspex.json (Linux: /etc/claude-code/managed-settings.d/auspex.json) and the user settings from ~/.claude/settings.json; both can be overridden with --managed-file and --settings-file. Pass --dry-run to see what it would do without writing. A timestamped .bak copy is written before any change, and the script prints how many hook entries it copied and removed.
If the settings file is owned by root
If ~/.claude/settings.json is owned by root rather than by the signed-in user, the script stops with a clear message rather than failing halfway. This is a device-level file-ownership problem rather than anything specific to auspex or Claude Code, and it also stops the user changing their own Claude Code settings, so it is worth correcting regardless:
sudo chown "$USER" ~/.claude/settings.json
Run that once per affected device — from your MDM as an administrator, or by the user in a terminal — then re-run the merge script.
Confirming the workaround worked
Have the user run Claude Code briefly so that it uses a tool, then check that the counter has moved:
auspex status --json
Workaround 2 — Define the hooks in Claude Code remote settings (last resort)
Because remote settings take precedence, defining auspex's hooks there puts them back in effect. Remote settings are managed at https://claude.ai/admin-settings/claude-code by your Claude organization administrators.
⚠️ Read the caveats before using this. This workaround works, but it takes a copy of auspex's internal wiring and pastes it into a policy that auspex does not own and cannot update, applies it to every Claude Code user in your organization, and interrupts each of them with a security approval dialog. Prefer Workaround 1 unless you genuinely cannot run a script from your MDM.
What it requires
- A Claude organization administrator, and the willingness to change organization-wide Claude Code policy. This is usually a different team from the one that owns your MDM, and the change is not scoped to the Span rollout.
- Accepting that every Claude Code user is prompted. Claude Code treats hooks delivered through remote settings as security-sensitive. Each user is shown a "Managed settings require approval" dialog listing the hooks the first time the policy reaches them, and must choose "Yes, I trust these settings" before the hooks take effect. Choosing "No" exits Claude Code — so a user who declines is left unable to work until they re-open and accept. The prompt appears once per organization policy, and publishing a changed policy prompts everyone again. Tell your users to expect it, or they are likely to decline.
- Organization-wide blast radius. There is no way to scope remote settings to a subset of users or machines. The policy reaches every Claude Code user in the organization — including contractors and users on devices that are not part of your AI trace tracking at all.
- Owning a hand-maintained copy of auspex's hook contract, forever. The block below is a snapshot of the ten events auspex captures and the exact command shape it invokes. auspex has no way to update it. When auspex adds an event, changes its command or its matchers, this copy silently goes stale and you lose the affected capture with no error anywhere — and fixing it means editing the policy by hand and re-triggering the approval prompt for every user. Workaround 1 has no equivalent of this, because it reads auspex's own file.
- A managed auspex install on the device. The commands bind auspex's fixed machine-wide install path,
/usr/local/bin/auspex. A user who self-installed auspex has the binary under~/.auspex/bininstead, and this workaround will silently do nothing for them. (Self-installs are not affected by this issue in the first place — they wire the user-tier hooks, not the managed drop-in.)
The hooks block
Add the hooks block below to your existing Claude Code remote settings, keeping the rest of your policy unchanged.
Every command resolves the auspex binary at its fixed managed location and runs it only if it is actually present, falling through to true otherwise. On devices where auspex is not installed at that path the hook does nothing, raises no error, and exits successfully. That is what makes it safe to publish to a mixed fleet. Do not add a "shell" key: a single policy also reaches Windows devices, and the commands are written for the POSIX shell Claude Code uses by default.
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code SessionStart || true"
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code UserPromptSubmit || true"
}
]
}
],
"PreToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code PreToolUse || true"
}
]
}
],
"PostToolUse": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code PostToolUse || true"
}
]
}
],
"PostToolUseFailure": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code PostToolUseFailure || true"
}
]
}
],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code SubagentStart || true"
}
]
}
],
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code SubagentStop || true"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code Stop || true"
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code PreCompact || true"
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "P=/usr/local/bin/auspex; [ -x \"$P\" ] && \"$P\" hooks capture claude-code SessionEnd || true"
}
]
}
]
}
}
Settings that can conflict with auspex's hooks
The settings below are unrelated to the override described above, but each one can stop auspex's hooks from running. If Claude Code traces stop arriving after a policy change, check these first.
disableAllHooks
Disables all hooks and status line execution, auspex's included. When set to true in managed settings, users and projects cannot override it. There is no exemption for managed or vendor hooks — if this is on, auspex captures nothing from Claude Code.
allowManagedHooksOnly
Managed settings only. Prevents user, project, and plugin hooks from loading; only managed and SDK hooks run.
This does not affect a standard auspex installation, whose hooks live in the managed drop-in and count as managed hooks. It does, however, break Workaround 1 above, which works by copying those hooks into the user's own settings.json — user-level hooks are exactly what this setting blocks. If you rely on that workaround, leave allowManagedHooksOnly unset. It also breaks a self-install of auspex, which wires the user tier by design.
strictPluginOnlyCustomization
Managed settings. Blocks non-plugin customization sources for the surfaces you list. If the value is true, or an array containing "hooks", only plugin-provided hooks are allowed and auspex's hooks will not run.
Settings that are safe
allowedHttpHookUrls and httpHookAllowedEnvVars apply only to HTTP hooks. auspex installs command hooks, which these settings do not affect — including "allowedHttpHookUrls": [], which blocks all HTTP hooks but leaves auspex working normally.