Background: how usage data flows
Unbound captures Claude Code usage in one of two modes. Knowing your mode tells you which pipeline to debug:
The hook lives at
~/.claude/hooks/unbound.py for individual installs, or /Library/Application Support/ClaudeCode/hooks/unbound.py (wired via managed-settings.json) for MDM-deployed installs. Check 1.2 detects which one you have. This page covers Claude Code; Cursor, Codex, and Copilot use similar but separate wiring, so for those tools gather the equivalent evidence and contact support.
Three facts explain most “no data” cases:
- Telemetry delivery fails open, by design. Your team’s coding flow comes first: if the hook can’t deliver usage data, Claude keeps working normally and the hook records the error in a local log (plus a rate-limited report to Unbound when the network allows). The flip side of that guarantee: “Claude works fine but no data in Unbound” means a delivery problem, not proof things are okay. Non-blocking hook errors don’t appear in the Claude UI (only a hook exiting with code 2 surfaces to the model). To watch hooks execute live, run
claude --debugor toggle verbose output with Ctrl+O; the local logs in check 1.6 give you the full history. Policy enforcement is separate: when the policy engine is unreachable, your org’s failure setting decides whether tool calls are allowed (the default) or blocked, and blocked calls show an explicit “policy engine could not be reached” message. - Hooks load only at Claude startup. Claude Code reads its hook configuration once, when a session starts. After any fix, fully quit Claude Code and relaunch it from a new terminal so the fresh configuration loads.
unbound statusconfirms login and connectivity;unbound doctorchecks the hook wiring.statusverifies your credentials, role, and that the Unbound API is reachable.unbound doctorgoes a layer deeper: per tool it verifies the config, the hook script, and the env wiring (and validates your API key), reporting each as healthy / tampered / not set up / managed by MDM. Run it first — it does in one command what checks 1.2 and 1.6 below do by hand.- Organization-managed Claude settings outrank local ones. Claude Code applies settings from several scopes, and organization-managed scopes — delivered from your company’s Claude admin console, or as a device policy file from IT — take precedence over
~/.claude/settings.json. A policy that restricts hooks turns an otherwise healthy individual install silent: no error log, no warning, nothing in--debug. Check 1.9 detects this in seconds.
Phase 1: gather state (read-only)
Run every block and save the outputs.1.0 Which mode is this machine in?
GATEWAY, stop here: checks 1.2 to 1.9 and the decision table apply to subscription mode only. On a gateway machine, “no data” usually means the routing env var or the app key, not hooks; gather 1.1 and 1.7 and go to Phase 5.
1.1 CLI status
Logged in Yes, your work email, your org name, and API status Connected.
If it shows Logged in No, keep gathering — the hook authenticates with its own stored key (check 1.4), so a logged-out CLI alone does not stop telemetry. It does mean the Fix 2 install step will open a browser to authenticate, and if check 1.4 also comes back empty, the stored credential is gone with the login (row D5).
1.2 Hook wiring in settings.json (the most important check)
~/.claude/settings.json) or an MDM-managed one (managed-settings.json); MDM-managed wiring is intentionally outside the user’s home directory.
1.3 Hook script present and executable
x permission bits (e.g. -rwxr-xr-x). It is normal for the other path to be absent. The explicit FAIL line (or a missing file at the locus 1.2 named) is a D1 signal; if 1.2 found no wiring either, both checks point at the same absent install.
1.4 API key resolvable
~/.unbound/config.json, so an unset env var alone is fine. One ownership detail: the ls -l line should show your username as the owner. If it shows root, the file was written by a setup command run under sudo from this account, and the hook — which runs as you — cannot read it; decision row D5 covers this. (The individual setup is designed to run without sudo; only the MDM fleet flow uses it.)
1.5 Gateway-mode residue (matters if you are in subscription mode)
1.6 Local hook logs (did the hook ever run, and did sends fail?)
error.log: occasional Exception in main: [Errno 32] Broken pipe entries are benign (Claude closed the pipe after the hook already sent its data), and sporadic Hook API error: Command '...' timed out after 20 seconds entries are transient network or gateway latency. On their own, neither indicates broken telemetry. The signals that matter are API request failed and Exception in send_to_api entries timestamped after your recent activity; entries older than your last successful usage are history, not the current fault. Hook API error: entries belong to the policy-check path, not telemetry delivery, and do not explain missing usage data. A self_update error: [Errno 2] No such file or directory: ... unbound.py entry means the hook script was missing while the wiring still pointed at it, which is exactly the state this page repairs: treat it as D1. A Failed to read config file: [Errno 13] Permission denied entry means the hook ran but could not read its credential — match it against check 1.4’s ownership line and treat it as D5. And if the audit log shows recent activity while 1.2/1.3 FAIL, that is not a contradiction: the entries predate whatever removed the wiring or script — still D1.
1.7 Network reachability
000, or TLS errors mean a network/proxy block.
1.8 Live end-to-end telemetry test (sends one labeled synthetic event)
This sends one synthetic usage event through the real delivery pipeline. It appears in your org’s Unbound dashboard as a zero-token claude-code event whose prompt starts with[unbound-diagnostic], which is the point: if this passes, Phase 4 has a guaranteed event to look for. Run it while no other Claude Code conversation is mid-prompt on this machine, and note that it appends two entries to the local audit log.
API request failed or Exception in send_to_api line timestamped after the test. The hook always exits 0, so the exit code is not a signal; silence in error.log after a real send attempt means the gateway accepted the event.
FAIL: a new API request failed: curl: (7) or curl: (28) line means network egress to Unbound is blocked (Fix 4); a curl: (56) ... 401 or curl: (22) ... 401 line means the key was rejected (Fix 2).
1.9 Organization-managed Claude policy settings (read-only)
Claude Code honors organization-managed settings before anything in your home directory. They arrive two ways: cloud policy set in your company’s Claude admin console (cached locally at~/.claude/remote-settings.json, refreshed at session start), or a device policy file installed by IT/MDM. Either can carry disableAllHooks or allowManagedHooksOnly, which skip user-level hooks at session start — silently, with no entry in any local log.
PASS. A FAIL here is the explanation for a machine where everything else passes — including 1.8, which still passes in this state because it exercises the delivery pipeline directly while the policy only prevents Claude from loading the hook into sessions. The decision table still applies top-down: if D1 also matched (broken wiring and a restrictive policy), repair the wiring with Fix 2 first, then re-run Phase 1 — this row will still be here.
Reading the output: a present but empty policy file (restrictions=None) is inert at either layer and is not a finding. For the cloud file it is the normal state for claude.ai Team/Enterprise accounts — Claude Code checks for org policies at startup and caches the answer even when there are none; for the device file it usually means a staged or placeholder MDM config. Presence of a file is not the signal; the restriction keys are. Two absence rules: if the cloud file is absent on a Team or Enterprise account (see the subscriptionType field from the cross-check below), start one Claude session and re-run this check so the cache gets populated; on personal plans (pro, max), an absent cloud file is expected and means nothing.
Two cross-checks:
- Human-only: in an interactive Claude Code session,
/statusincludes a Setting sources line listing every configuration scope that loaded. AnEnterprise managed settingsentry marked(remote)means cloud policies are active on this machine; a file-based entry points at the device policy. Slash commands are not available to shell-driven agents (claude -p "/status"reports them unavailable) — agents should ask the human to read this line out, not attempt it. - Shell-safe:
claude auth status --jsonshows which Claude account, organization, and plan the machine is signed into — cloud policies follow the signed-in organization, so this tells you exactly whose admin console to ask about when more than one workspace is in play.
Phase 2: decision table
Match your Phase 1 results top-down; the first matching row is your diagnosis.Phase 3: fix ladder
Start at the fix your decision row names; move further down the ladder only if Phase 4 still fails after it.Agents: tiers 1 and 2 are safe to run. Tier 3 (
nuke) removes Unbound from every AI tool on the machine, so confirm with the human before running it. Fix 5 changes nothing locally — it is a coordination step with the organization’s Claude admin; prepare the message, let the human send it.Fix 1: restart properly
Agents: restarting Claude Code ends your own session. Before handing this step to the user, write a resume file at
~/unbound-diagnosis.md containing your Phase 1 outputs, the decision row you matched, and any fix already applied, then tell the user: after relaunching, point the new session at this page and say “resume from ~/unbound-diagnosis.md”.1
Fully quit Claude Code
Close every session, tab, and window, not just the conversation.
2
Open a brand-new terminal
So refreshed environment variables load.
3
Relaunch Claude Code from that terminal
Hooks and environment only load on a fresh process.
Fix 2: clean re-install
--clear needs no login at all, and the install command reuses your stored unbound login credential from ~/.unbound/config.json when check 1.4 found one. If 1.4 came back empty — or you have never logged in — the install command opens a browser to authenticate, so have your Unbound login ready. Notes:
- Precondition: both 1.7 checks must PASS before running
--clear. The clear happens first; if the network can’t reach GitHub raw, the reinstall fails and the machine ends up with no Unbound config at all. Resolve D7 first. - If check 1.4 reported the config file unreadable (owner
root): restore ownership first withsudo chown $(id -un) ~/.unbound/config.json, then run the clear/install pair. This state appears when a setup command was previously run withsudofrom this account; run the individual setup withoutsudogoing forward. - The clear step removes Unbound config for every tool it can manage (both Claude Code and Codex modes, Cursor, Copilot, Augment, and Gemini CLI); the install step then sets up the five default tools (Claude Code, Cursor, Codex, Copilot, Augment). That is intentional: it removes cross-mode and cross-tool residue in one pass. If you use Gemini CLI through Unbound, re-run its setup afterwards with
unbound setup gemini-cli. - Every tool must show a green check and the run must end with
All tools configured. If any tool fails, do not proceed; check 1.7 (network) and retry, on a different network if needed. - Optional: add
--backfillto the install command to also upload local session history and fill the data gap from the outage. - Re-run check 1.2. If any event still FAILs after a successful run, go to Fix 3.
- Then do Fix 1 (restart). Always.
Fix 3: nuke and repave
Use when Fix 2 reports success but checks still fail, or when mode-switch residue keeps coming back:nuke also deletes the stored login (~/.unbound/config.json), so the follow-up setup will ask you to authenticate in a browser; on a remote or headless machine, plan for that before nuking. If check 1.2 still reports hook entries after a clear or nuke, those entries use a quoted command format the cleaner cannot currently match; remove them from settings.json by hand or escalate (Phase 5).
Then either run the Fix 2 install command again (unbound setup --all), or re-onboard everything (all tools plus device discovery) with the onboard command from the setup page (gateway.getunbound.ai/setup → My Device).
Then Fix 1 (restart). Re-run all of Phase 1.
Fix 4: delivery blocked (hook fires, sends fail)
1
Re-check network reachability
Re-run 1.7. If
api.getunbound.ai is unreachable, you are behind a corporate proxy or firewall. Ask IT to allow api.getunbound.ai (and backend.getunbound.ai), or test on another network to confirm.2
Refresh credentials
If reachable but error.log shows auth-style failures: run Fix 2 to refresh the key, then Fix 1.
Fix 5: align with your organization’s Claude policy
Nothing on this machine needs repair; this is a coordination step. Re-running setup will not change the outcome — the policy is applied at session start, above all user-level configuration.1
Identify the policy source from check 1.9
cloud policy means it is set in your company’s Claude admin console; device policy means your IT/MDM team ships it to the machine.2
Loop in the owning admin
Two clean resolutions, in order of preference: (a) the admin adds the Unbound hooks block to the managed settings themselves, so the hooks ride the policy channel that already wins precedence (contact us for the exact JSON for your org); (b) the admin relaxes the restriction (
allowManagedHooksOnly / disableAllHooks) for your org or fleet.A memory check that helps date the policy: when cloud-managed settings first arrive (or change), Claude Code shows the user a review prompt for the new settings. Asking affected developers whether they recall approving new managed settings — and roughly when — both confirms the policy path and tells the admin which change to look at.3
Restart and verify
Managed settings refresh at session start. After the admin-side change, do Fix 1, re-run check 1.9 (should now PASS, or show the Unbound hooks inside the policy), then Phase 4.
Phase 4: verify it actually works
1
Start a NEW Claude Code session
After fixing and restarting, run two or three real prompts.
2
Check the local evidence
3
Check the Unbound dashboard
Your session should appear at gateway.getunbound.ai, usually within a minute (measured ingestion is around ten seconds). Wait five minutes before treating this step as failed.
Phase 5: escalate to Unbound support
If Phase 4 fails after the fix ladder, the remaining suspects are on our side (key/app mapping, ingestion), and we want to hear from you. Compile this bundle:- Full output of all Phase 1 checks
- The exact tier(s) of Phase 3 you ran and their console output
unbound --versionand your OS version- The timeframe of the missing data and the affected user emails (this is support’s first question; including it saves a round trip)
Agents: do not send this bundle anywhere on your own. Compile it, confirm it is redacted, present it to the user, and end with an open question, for example: “Your diagnostic bundle is ready and sanitized. How would you like to send it: should I locate your organization’s shared Unbound Slack channel and draft the message there, draft an email to support@unboundsecurity.ai for your review, or would you rather send it yourself?” The user decides where escalation goes; you prepare it.
A note on switching modes
Switching modes (gateway → subscription or back) swaps one delivery pipeline for the other. If the switch was interrupted partway (a blocked download on a corporate network, for example), settings from both modes can linger together and data stops flowing. If your data stopped right when you switched, go straight to Fix 2 (clean re-install), or Fix 3 (nuke) if anything survives it. The switch also changes which organization policies matter. Gateway mode does not use hooks, so a Claude policy restricting them (check 1.9) can sit on a machine or org indefinitely without any visible effect — until the switch to subscription mode, at which point telemetry goes quiet from the first session. “Data stopped exactly when we switched” therefore does not imply something changed at that moment: run 1.9 before concluding the switch itself misfired, and if it FAILs, go to Fix 5 — re-running setup (Fix 2/3) will not change the outcome.For admins: MDM / fleet rollouts
- A device appearing in Discovery (inventory of installed AI tools) does not mean coding telemetry is flowing. Discovery and telemetry are separate pipelines with separate keys. Verify a fleet rollout by checking that usage events arrive; usage events are the ground truth, and dashboard fields that lag a successful install should not be treated as failure signals.
- MDM-pushed setup (
sudo unbound onboard) installs system-level hooks that need sudo to tamper with. On MDM devices the hook wiring lives in/Library/Application Support/ClaudeCode/managed-settings.json, not the user’s home; checks 1.2 and 1.3 detect this automatically, and the per-user checks (1.4 to 1.6) still apply per home directory. See MDM Integrations. - Success criterion for a fleet push: every target device produces at least one usage event within 24 hours of a developer using a wired tool. Devices that check in but never produce usage are exactly the failure mode this page diagnoses.
- If your organization manages Claude Code settings from the Claude admin console, two properties matter for telemetry: those settings replace the device policy file rather than merging with it, and
disableAllHooks/allowManagedHooksOnlyapply fleet-wide to every individually-installed hook. Two fleet patterns point here: usage that went quiet at the same moment a Claude policy shipped, and — easier to miss — a fleet that switched from gateway to subscription mode and never produced hook telemetry at all. Gateway mode does not use hooks, so a restriction can predate the switch by months without a single symptom. In both cases check 1.9 on any one affected device confirms it in seconds, and Fix 5 describes the two resolutions. We can provide the exact hooks JSON for your console policy on request.

