auspex: Claude Code Remote Settings and Managed Hooks (Windows)

Last updated: October 7, 2026

This page documents Claude Code behavior that can stop auspex from capturing Claude Code activity on managed (MDM) installations, and the one-line policy change that fixes it. It affects Claude Code only — auspex's capture from Cursor, Codex, GitHub Copilot and VS Code is unaffected.

Why the managed hooks stop firing

auspex installs its hooks as a dedicated, auspex-owned file alongside your own managed settings, so it never modifies your configuration:

  • Windows: %ProgramFiles%\ClaudeCode\managed-settings.d\auspex.json
  • macOS: /Library/Application Support/ClaudeCode/managed-settings.d/auspex.json
  • Linux: /etc/claude-code/managed-settings.d/auspex.json

Claude Code normally reads this drop-in and runs the hooks it defines. It stops doing so when your organization publishes remote settings (also called server-managed settings, configured in the claude.ai admin console).

The cause is Claude Code's precedence within the managed tier. Several mechanisms can deliver managed settings to one machine, and Claude Code ranks them:

  1. Remote settings, fetched from claude.ai or a Claude apps gateway
  2. MDM or OS-level policy — on Windows, the HKLM\SOFTWARE\Policies\ClaudeCode registry value
  3. Managed settings files — managed-settings.json and the managed-settings.d\*.json drop-in, merged together — this is where auspex's hooks live
  4. The HKCU registry

By default Claude Code applies the highest-ranked source that delivers at least one policy key, and ignores the rest entirely — it does not merge them. This behavior is controlled by the managedSourcesBehavior setting, whose default value is "first-wins".

So once remote settings carry any policy key, the whole file-based source is dropped, auspex's drop-in with it.

The trigger is not a hooks block. This is the single most misdiagnosed part of this issue. Any policy key in remote settings is enough — a permissions.deny list, a model value, a cleanupPeriodDays number. A policy that never mentions hooks will still stop auspex's hooks from running. We have reproduced this with a remote policy whose entire content was one permissions.deny rule.

This is not platform-specific. Earlier revisions of this page described it as Windows-only behavior; that was a misreading of the same precedence rule, which applies identically on macOS and Linux. The macOS page carries the same guidance.

Why this is so hard to spot

Nothing is broken, so nothing reports an error:

  • Claude Code still reads and watches the drop-in file — its debug log names the managed-settings.d directory among the settings files it is watching. It reads the source and then discards it in favour of the higher-ranked one. It logs no warning for the sources it skips.
  • Because the file is present and correct on disk, auspex status reports it as correctly wired.
  • If your managed-settings.json also carries an env block — an OpenTelemetry exporter configuration, for instance — that block keeps working. The env block is one of a small set of keys Claude Code merges per-variable across every admin source regardless of this precedence rule. So Claude Code's own telemetry can continue flowing from a device on which auspex's capture has entirely stopped. Two things that look like they should fail together do not.

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 --verbose reports 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 apply the source they live in.
  • auspex status --json shows an "event_counter" under queue that never moves, with "depth": 0 and 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 fastest check is /status inside an interactive Claude Code session. Its Setting sources line names the managed source Claude Code used and the ones it skipped. If it names remote settings as the source used and the managed settings file as skipped, that is this issue.

claude doctor reports the same fetch outcome on its Managed settings (remote) line (Claude Code v2.1.248 or later).

For a definitive answer, use Claude Code's debug log, which records every hook it runs and every settings source it loads. Run this as the signed-in user on an affected device, from a project directory. A managed (.msi) install puts auspex on the machine PATH, so the bare command works; if it does not, use the full path & "C:\Program Files\auspex\bin\auspex.exe".

claude --debug-file $env:TEMP\cc-debug.txt -p "Run 'echo diag' with your Bash tool, then reply DONE."
Select-String -Path $env:TEMP\cc-debug.txt -Pattern "auspex|Watching for changes in setting files|Remote settings:"

Read the result as follows:

  • Lines that name auspex.exe and end in completed with status 0 — the hooks are running. This issue is not what you are seeing.
  • No auspex lines at all — Claude Code is not running the drop-in's hooks. That is this issue.
  • Remote settings: Applied new settings successfully, or Using cached settings (304), confirms 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 does normally mention the managed-settings.d drop-in directory even when this issue is active. Its presence does not mean the drop-in is being applied — do not read it as a pass.

The log appends rather than truncates across runs, so read the block with the newest timestamps.

You can corroborate with auspex's own counter and the supporting state:

auspex status --verbose
auspex status --json | ConvertFrom-Json | Select-Object -ExpandProperty queue
Get-Content "$env:USERPROFILE\.claude\remote-settings.json"
Test-Path "$env:ProgramFiles\ClaudeCode\managed-settings.d\auspex.json"

You are seeing this issue if all of the following hold: status --verbose reports no failing checks and capture_wiring passes, the queue's event_counter does not move after the user has genuinely used Claude Code, the drop-in file exists, and remote-settings.json returns a policy (anything other than the empty {} placeholder).

Workarounds

Workaround 1 is the fix. Workaround 2 exists only for fleets that cannot use it.

Workaround 1 — Set managedSourcesBehavior to "merge" (recommended)

Add one key to your Claude Code remote settings, at https://claude.ai/admin-settings/claude-code:

{
  "managedSourcesBehavior": "merge"
}

Keep the rest of your policy unchanged — this key goes alongside whatever you already publish.

Under "merge", Claude Code applies every admin source you deploy instead of only the highest-ranked one, and combines them by kind of key. hooks is a list-kind key, so the entries from every source combine: your remote policy's hooks, if you have any, and auspex's drop-in hooks all run. Capture resumes with no further changes on any device.

Because the hooks keep coming from auspex's own drop-in, they keep the exact form auspex writes — on Windows, auspex.exe as the command and its arguments as a separate args list, so Claude Code starts auspex directly rather than through a shell. This workaround needs no hand-written hook commands and so cannot get that detail wrong.

Where to set it. Claude Code reads this key only from the highest-ranked source that carries either the key or a policy key, so a lower source cannot opt itself into merging with the one above it. If you publish remote settings, it must go there. Also add it to your HKLM\SOFTWARE\Policies\ClaudeCode registry policy so that devices which never receive remote settings — users on a third-party model provider, for example — are covered by the same policy.

Requirements.

  • Claude Code v2.1.242 or later on the device. Older clients ignore the key and keep the "first-wins" behavior; use Workaround 2 for those. Check with claude --version.
  • A Claude organization administrator to edit remote settings, if that is where the key goes. It is a single key you set once and never revisit.

Pros. auspex keeps owning its own hook definitions, so nothing can drift out of date — when auspex adds an event or changes its command, the drop-in changes and the merged policy picks it up with no action from you. No approval prompt for any user, because the hooks are read from a file on the device rather than delivered from the server. No per-device scripting and nothing written into user profiles. It restores the intended managed-tier behavior rather than working around it. One policy covers your Windows, macOS and Linux devices alike.

Cons. It is an organization-wide policy change and cannot be scoped to a subset of users. Because "merge" also makes Claude Code apply list entries such as permissions.allow rules from every lower-ranked source, turn it on only when the sources below your highest one are under an administrator's control — on Windows %ProgramFiles%\ClaudeCode\ is administrator-writable only, so that is normally the case. Requires a recent Claude Code.

Confirming it worked

In an interactive session, /status should now show the sources combined rather than one skipped. Then have the user run Claude Code briefly so that it uses a tool, and check that the counter has moved:

auspex status --json | ConvertFrom-Json | Select-Object -ExpandProperty queue

The debug log should again show lines that name auspex.exe and end in completed with status 0.

Workaround 2 — Copy the managed hooks into user settings via MDM

Use this when your devices run Claude Code older than v2.1.242, or when you do not control your organization's remote settings and cannot get the key above published.

Run a script from your MDM that reads the auspex-owned managed drop-in and merges its hooks into the user's own %USERPROFILE%\.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 "args" list, the per-hook "timeout", and the tool matchers auspex writes are all preserved exactly — which matters, because auspex's Windows hook runs auspex.exe directly with its arguments in "args", and only works with that list attached.

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.

After upgrading auspex. Earlier auspex versions wrote each hook as a PowerShell call (& '…\auspex.exe' hooks capture claude-code <Event> with "shell": "powershell"). Current versions write it so that Claude Code starts auspex.exe directly, which makes every hook noticeably faster. Upgrading the .msi updates the managed drop-in, but the copy in user settings changes only when the script next runs — so run it once right after the upgrade rather than waiting for the schedule. Use the current version of the script: it recognizes auspex hooks in both forms and replaces the old ones, so the user never ends up with both.

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. Works on Claude Code versions too old for Workaround 1.

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 install cannot 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.

For the same reason, do not combine this workaround with Workaround 1. Once "merge" is in force the drop-in's hooks run again, and a copy in user settings would capture every event a second time. If you are moving from this workaround to Workaround 1, strip the auspex entries from each user's %USERPROFILE%\.claude\settings.json before publishing the "merge" key.

The script

Download Merge-AuspexHooks.ps1 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 profile:

powershell -ExecutionPolicy Bypass -File .\Merge-AuspexHooks.ps1

By default it reads the managed drop-in from %ProgramFiles%\ClaudeCode\managed-settings.d\auspex.json and the user settings from %USERPROFILE%\.claude\settings.json; both can be overridden with -ManagedFile and -SettingsFile. A timestamped .bak copy is written before any change, and the script prints exactly which hooks it added and removed.

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 | ConvertFrom-Json | Select-Object -ExpandProperty queue

Settings that can conflict with auspex's hooks

The settings below are unrelated to the precedence behavior 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. Neither workaround above helps.

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 — and it does not affect Workaround 1, for the same reason. It does break Workaround 2, 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.

Note that under managedSourcesBehavior: "merge" this setting is a lock: the strictest value any admin source sets applies.

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.

Related