[agenticwork]
← blog

inside claude code: a complete technical analysis of anthropic's leaked cli source

On March 31, 2026, Anthropic accidentally shipped a 59.8 MB source-map file inside the @anthropic-ai/claude-code v2.1.88 package on npm. The file — cli.mjs.map — contained the full, unminified TypeScript source of Claude Code, Anthropic's flagship CLI agent. Within hours, copies were mirrored across GitHub. By the time Anthropic pulled the package and published v2.1.89 with the source map removed, the damage was done.

The leak was first reported by Axios and quickly picked up by The Register, Fortune, VentureBeat, CNBC, and Cybernews. Security researcher Chaofan Shou is credited with the discovery. The Hacker News and Zscaler ThreatLabz published detailed security analyses. Alex Kim's independent technical analysis was among the first deep dives.

This is Anthropic's second source leak in under a year. Fortune noted it came just days after Anthropic accidentally revealed details about its internal "Mythos" project. And on April 2, Congressman Gottheimer sent a letter to Anthropic demanding answers about the company's data handling practices.

We read every line against a fresh static analysis of the tree dated 2026-03-31. This post is an audit report — every claim cites the file and line that substantiates it. Where our earlier reporting overstated a finding, we say so and correct the record.

0. Correction notice

An earlier version of this post made three claims about telemetry payload content that are not supported by the source. We have since performed a line-by-line static analysis and are correcting the record here.

  • Corrected: "Datadog receives ~64 whitelisted event types" → the allowlist in src/services/analytics/datadog.ts:19-64 is exactly 44 event names. Counted programmatically (awk over the set literal → 44).
  • Corrected: "The Bash tool sends the command being executed to telemetry." Wrong. The tengu_tool_use_success and tengu_tool_use_error events (src/services/tools/toolExecution.ts:1331 and :372) contain only toolName (passed through sanitizeToolNameForAnalytics), durationMs, toolResultSizeBytes, queryChainId, and queryDepth — plus, for errors, a message string typed as the compile-time marker AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS. The only Bash-specific event that carries command-derived content is tengu_bash_security_check_triggered, and even that sends a numeric checkId + subId (taxonomy codes), not the command. Command text goes to the inference API (expected), not to Datadog/1P.
  • Corrected: "File operations send file paths for Read, Write, Edit, Glob, Grep." Wrong. tengu_session_file_read (src/tools/FileReadTool.ts:1069) sends totalLines, readLines, totalBytes, readBytes, offset, limit, ext (file extension only, via getFileExtensionForAnalytics()), and messageID. No path. Same pattern across FileEdit / FileWrite / Glob / Grep.

Two things follow from these corrections. First, the enterprise thesis of this post stands — the telemetry profile is significant, the kill-switches are server-side, and the credential fallback on Linux/Windows is plaintext — but it has to be built on the claims the source actually supports. Second, Claude Code's client-side plumbing around PII is more careful than our first read suggested: a compile-time marker type is a real forcing function, and the _PROTO_* convention routes PII-tagged fields to a privileged BigQuery column while stripping them from Datadog. We cover each below with citations.

1. architecture overview

Claude Code is a terminal-native AI agent built on React and a custom fork of Ink (React for CLIs). The application is TypeScript, bundled with Bun, and ships as a single cli.mjs monolith weighing roughly 803 KB minified. The leak comprises approximately 513,000 lines of TypeScript across 1,906 source files.

entry point and pipeline

The application entry is main.tsx. The core query pipeline flows through a QueryEngine that orchestrates the interaction with the inference API (src/services/api/client.ts):

main.tsx → bootstrap() → AppState init → QueryEngine → query.ts → /v1/messages
 ↓
 Tool dispatch → Permission check → Execute → Response

services layer

Below the query engine sits a services layer for cross-cutting concerns. Relevant modules for this analysis:

  • Analyticssrc/services/analytics/ — Datadog + 1P + GrowthBook exporters.
  • OAuthsrc/services/oauth/ — token management, refresh, PKCE.
  • MCP — Model Context Protocol client for external tool servers.
  • Secure storagesrc/utils/secureStorage/ — platform-specific credential backends.
  • Remote-managed settingssrc/services/remoteManagedSettings/ — hourly-polled enterprise config.
  • Policy limitssrc/services/policyLimits/ — hourly-polled org policy.
  • Telemetrysrc/utils/telemetry/ — OpenTelemetry meter / tracer / logger providers.

2. outbound data flows — five pipes, not one

Claude Code maintains five conceptually distinct outbound flows. They are independent pipes with independent authentication, batching, and opt-outs.

FlowDestinationPayloadDisablable?
Inferenceapi.anthropic.com/v1/messages (or Bedrock / Vertex / Foundry)Prompts, tool results, conversation historyNo — the app cannot function without it
OAuth / Accountapi.anthropic.com, platform.claude.com, claude.aiTokens, profile, org/role, quota, referralOnly for non-authenticated flows
Telemetry (Datadog)http-intake.logs.us5.datadoghq.com44 allowlisted event names plus redacted metadataYes — DISABLE_TELEMETRY=1 or tengu_log_datadog_events=false
Telemetry (1P)api.anthropic.com/api/event_logging/batchAll events; OTel-batched protobufYes — DISABLE_TELEMETRY=1; disk-queued on failure
Remote Configapi.anthropic.com (GrowthBook + managed settings + policy limits)Device + org + email attributes out; JSON feature values inYes, but causes stale-cache mode

In addition, ancillary flows exist for feature-specific paths: downloads.claude.ai for plugin marketplace and auto-updates, storage.googleapis.com for release binaries, raw.githubusercontent.com for the CHANGELOG, mcp-proxy.anthropic.com for MCP proxying, and a WebSocket to api.anthropic.com for remote / collaborative sessions.

All HTTP traffic honours HTTP_PROXY / HTTPS_PROXY / NODE_EXTRA_CA_CERTS. Enterprise TLS inspection proxies can observe all of the above — but the OAuth flow is PKCE-protected, limiting what a MITM'd proxy can do with auth credentials.

3. the telemetry system — what actually gets sent

three pipelines, not two

Our earlier post described two telemetry backends. The source shows three, plus a fourth that piggybacks on the 1P pipe:

  1. Datadog — 44-event allowlist (src/services/analytics/datadog.ts:19-64), batch of 100 or 15-second flush.
  2. First-party (1P) event logging — everything else; OTel BatchLogRecordProcessor → protobuf → /api/event_logging/batch (src/services/analytics/firstPartyEventLoggingExporter.ts:112-120).
  3. BigQuery metrics — periodic 60-second OTel metric export to api.anthropic.com/api/claude_code/metrics (src/utils/telemetry/bigqueryExporter.ts:47). Gated by a 24-hour-cached organisation-level opt-out check.
  4. GrowthBook experiment exposures — every feature-flag evaluation fires a growthbook_experiment event onto the 1P pipe.

the Datadog client token and the event allowlist

The Datadog endpoint is literal: https://http-intake.logs.us5.datadoghq.com/api/v2/logs (datadog.ts:12-13). The public client token pubbbf48e6d78dae54bceaa4acf463299bf is sent in DD-API-KEY (datadog.ts:14). Batch policy is 100 events or a 15-second flush, configurable via CLAUDE_CODE_DATADOG_FLUSH_INTERVAL_MS (datadog.ts:15-17).

The event-name allowlist at datadog.ts:19-64 is 44 entries long, not the 64 we reported earlier. Any event not in this set is silently dropped at the Datadog boundary and sent only to the 1P backend.

Datadog does not receive your user ID

A detail the original post missed: the user identifier attached to Datadog events is not the raw user ID. Instead it is a SHA-256 hash of the user ID modulo 30 — yielding a bucket in the range 0–29 (src/services/analytics/datadog.ts:281-299). Raw account UUIDs and emails are sent to the 1P backend, which is operated by Anthropic directly, but not to Datadog. This is a deliberate split.

Similarly, the git remote URL is normalised and hashed to a 16-character SHA-256 prefix before it is attached to any telemetry event (src/utils/git.ts:283-338) — shipped as the rh field. The raw URL is not sent.

what the 1P backend receives

The 1P event schema is defined at src/services/analytics/firstPartyEventLoggingExporter.ts:670-759. Each event carries:

ClaudeCodeInternalEvent {
 event_id // UUID
 event_name // e.g. "tengu_api_success"
 client_timestamp
 session_id
 parent_session_id // for subagents / plan mode
 device_id // stable machine-level ID
 email // when OAuth is present
 auth { account_uuid, organization_uuid }
 env (EnvironmentMetadata) {
 platform, platform_raw, arch, node_version, terminal,
 is_ci, is_github_action, is_claude_code_remote, is_conductor,
 version, build_time, deployment_environment,
 wsl_version, linux_distro,
 github_actor_id, github_repository_id, github_repository_owner_id,
 remote_env_type, coworker_type, tags, container_id
 }
 process // base64(JSON of uptime, memory, cpu)
 _PROTO_skill_name // PII-tagged → privileged BigQuery column
 _PROTO_plugin_name
 _PROTO_marketplace_name
 additional_metadata
}

The email field is the one that matters: whenever the user is signed in with Anthropic OAuth, their account email is attached to every 1P event. There is no separate toggle — DISABLE_TELEMETRY is the coarsest (and only) control.

The _PROTO_* fields are PII-tagged and are stripped from the Datadog payload before assembly (firstPartyEventLoggingExporter.ts:714-726). They flow to a privileged BigQuery column on the 1P side only.

what the 1P backend does not receive

This is where the original post was wrong. File paths, prompt content, file contents, shell command strings, and shell stdout do not reach either telemetry pipe under the default configuration. They travel to the inference API (expected — they're the product) but not to analytics.

Data classTo inference APITo telemetryCitation
Prompt textVerbatimNot sentsrc/services/api/claude.tsuserMessageToMessageParam
File contents (Read/Grep)VerbatimNot sentsrc/tools/FileReadTool/
Shell outputVerbatimNot sentBashTool
File pathsIn tool argsNot sent — only extFileReadTool.ts:1069, getFileExtensionForAnalytics()
Git remote URLOnly if user runs git16-char SHA-256 prefixsrc/utils/git.ts:283-338
EmailNot directlyIn every 1P event if OAuthfirstPartyEventLoggingExporter.ts:670
Account UUID / org UUIDIn metadataIn every eventStable identifier
Device IDIn metadataIn every eventGenerated locally, persists
API keyHeader onlyNeverNever serialised into events
MCP tool namesSentRedacted to mcp_tool unless officialmetadata.ts:70-113
Stack tracesNot sentError name only ("TypeError")gracefulShutdown.ts:299-333

the PII marker type — compile-time only

The primary client-side defence against PII in telemetry is a TypeScript marker type (src/services/analytics/index.ts:18-33):

type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = {
 [key: string]: string | number | boolean | undefined
}

Every event-accepting function requires the caller to use this type, asserting through the verbose name that the values are not code or paths. It is a compile-time check — a future diff or a TypeScript cast could bypass it. In practice it functions as a code-review forcing function rather than a runtime enforcement boundary.

two escape-hatch env vars that do ship prompt content

Two internal-use environment variables materially change what leaves the machine, and both are off by default:

  • OTEL_LOG_USER_PROMPTS=1 — when set, user prompt text is logged to OpenTelemetry (src/utils/telemetry/events.ts:13-19).
  • OTEL_LOG_TOOL_DETAILS=1 — when set, MCP tool names and details are logged rather than redacted (src/services/analytics/metadata.ts:86-88).

Neither is set by default. Both are documented in the source as internal / debug. A privacy-sensitive deployment should verify neither is present in its environment.

scale of instrumentation

Across the tree, 271 source files contain calls to logEvent(). This is pervasive instrumentation — every tool invocation, API call, error, startup, and permission decision is instrumented. The content is metadata, not payload, but the granularity is high.

4. credential storage — plaintext on Linux & Windows

The credential subsystem lives at src/utils/secureStorage/. The backend is selected at runtime by host OS:

PlatformPrimaryFallbackOn-disk pathMode
macOSKeychain (macOsKeychainStorage.ts)Plaintext if Keychain failsOS-managed — service name claude-cli (credentials)OS-managed
LinuxPlaintext (plainTextStorage.ts)~/.claude/.credentials.json0600
WindowsPlaintext~/.claude/.credentials.json0600

On Linux and Windows, OAuth refresh tokens, access tokens, and API keys are written in clear text to ~/.claude/.credentials.json with chmod 0600. The code surfaces the warning string "Warning: Storing credentials in plaintext." on write (plainTextStorage.ts:64). There is no libsecret / GNOME Keyring / KWallet / Windows Credential Manager integration in this build. Any process running as the same user can read these tokens, and they persist on disk unencrypted.

macOS keychain hardening

The macOS Keychain implementation is thoughtful — worth documenting:

  • Payload is hex-encoded to avoid plaintext in process listings (macOsKeychainStorage.ts:109).
  • stdin is preferred over argv when invoking the security binary, to prevent exposure via ps (:121-145).
  • A 30-second read cache reduces Keychain popups (:11).
  • A stale-while-error pattern serves the cached token if a refresh transiently fails (:57-62).

session transcripts on disk

Conversation histories are written to ~/.claude/projects/{sanitised-cwd}/{sessionId}.jsonl. File mode is 0600, directory mode is 0700. These transcripts are unencrypted — they contain whatever the user pasted, whatever Claude saw, and whatever any tool returned.

a partial list of on-disk persistent files

PathPurposeMode
~/.claude/settings.jsonGlobal user settingsumask
~/.claude/.credentials.jsonPlaintext credentials (non-macOS)0600
~/.claude/history.jsonlPrompt / command history (100 per project)0600
~/.claude/projects/{cwd}/{sid}.jsonlSession transcriptsfile 0600, dir 0700
~/.claude/pasted-content/Large clipboard pastes (MD5-keyed)0600
~/.claude/image-cache/{sid}/Images referenced in prompts
~/.claude/errors/{DATE}.jsonlError log (full stacks; internal users only)
~/.claude/mcp/{server}/{DATE}.jsonlMCP server errors
~/.claude/telemetry/1p_failed_events.*.jsonQueued events pending retry
~/.claude.jsonGrowthBook disk cache (ETag)
~/.claude/policy-limits.jsonPolicy limits cache

Of note: the disk-backed 1P retry queue. Failed telemetry batches persist across restarts at ~/.claude/telemetry/1p_failed_events.*.json and resume sending on the next connection. A user who uninstalls but does not clear this directory will still transmit queued events on reinstall.

5. remote configuration & kill-switches

Three remote-config systems run continuously against the client. Because each can alter client behaviour without a release, they are the most operationally sensitive channel.

GrowthBook feature flags

The SDK client lives at src/services/analytics/growthbook.ts (1156 lines) and runs in remoteEval mode — rules evaluated server-side, the client sending attributes and receiving pre-computed values. Attributes include device ID, session ID, platform, version, organisation UUID, account UUID, subscription type, rate-limit tier, GitHub-Actions context, and — when available — email (growthbook.ts:454-485).

Polling cadence:

  • External users: 6-hour refresh.
  • Internal (ant) users: 20-minute refresh.
  • ETag-keyed on (id, organizationUUID); disk cache survives restarts.
  • 5-second init timeout; stale-while-revalidate on failure.

Flags observed in the tree. Several of these are kill-switch-grade:

FlagEffect
tengu_max_version_configCaps auto-update to a server-chosen version. Anthropic can freeze clients at a given version via this flag.
tengu_log_datadog_eventsEnable/disable Datadog sink entirely.
tengu_frond_boricPer-sink kill-switch (JSON mapping).
tengu_event_sampling_configPer-event sampling rates.
tengu_1p_event_batch_configOverride 1P batch size, delay, base URL, auth.
tengu_strap_foyerEnable settings download on CCR/headless.
tengu_enable_settings_sync_pushEnable outbound settings sync.
tengu_herring_clockTeam-memory directory feature.
tengu_remote_backendRemote session enablement.

remote managed settings

Enterprise accounts poll api.anthropic.com/api/claude_code/settings every hour (src/services/remoteManagedSettings/index.ts:106,54). The server returns a Zod-validated, checksum-verified JSON settings object. Free and Pro users get {}. A user-consent prompt (checkManagedSettingsSecurity, :458) gates dangerous settings before they take effect.

policy limits

An analogous hourly poll against api.anthropic.com/api/claude_code/policy_limits (src/services/policyLimits/index.ts:127). Governs per-permission flags such as allow_product_feedback. In essential-traffic-only mode, certain policies fail closed rather than open (:502-526) — a conservative default worth knowing about.

rate-limit-header live kill-switch

Every Claude API response includes anthropic-ratelimit-unified-* headers. The client parses seven of them (src/services/claudeAiLimits.ts:376-435) and can act on status: rejected or overageDisabledReason to stop making requests mid-session. This is the finest-grained server-side control — effectively a per-user, live, in-band kill-switch that doesn't require a feature-flag poll.

no remote code execution

Searches for eval(, new Function(, require(-from-URL, and dynamic import-from-URL found no hits in the tree. Skills, agents, plugin prompts are Markdown/JSON read from disk. Plugin ZIPs are content-addressed ({sha}.zip) and extracted to a versioned cache directory. Managed settings are data, not code.

6. privacy controls — the two env vars that matter

The authoritative reference is src/utils/privacyLevel.ts, which defines three ordered levels:

LevelTriggerEffect
default(nothing set)Everything enabled.
no-telemetryDISABLE_TELEMETRY=1Datadog, 1P events, and the feedback survey are disabled. Auto-updates, Grove (privacy-policy UI), release notes, and model-capabilities queries continue to run.
essential-trafficCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1All non-essential network I/O disabled: telemetry + auto-updates + Grove + release notes + MCP registry + plugin marketplace + remote-session teleport events. The strongest privacy switch the binary exposes.

CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is the lever the original post should have named. It is the difference between "opt out of analytics" and "make this binary as quiet as possible without breaking inference."

provider routing also disables 1P telemetry

Setting CLAUDE_CODE_USE_BEDROCK=true, CLAUDE_CODE_USE_VERTEX=true, or CLAUDE_CODE_USE_FOUNDRY=true routes the inference request through AWS, Google, or Azure — and in each case all first-party telemetry is disabled (src/services/analytics/datadog.ts:164-171). The original post missed Azure Foundry; that is corrected here.

organisation-level metrics opt-out

An organisation can opt out of BigQuery metrics at the org level via api.anthropic.com/api/claude_code/organizations/metrics_enabled (24-hour disk-cached). This disables metrics for every user in the org.

OAuth-URL allowlist

OAuth endpoints are redirectable via CLAUDE_CODE_CUSTOM_OAUTH_URL, but the target must be on an allowlist (src/constants/oauth.ts). This is explicitly a FedStart-facing anti-credential-exfil safeguard — an arbitrary attacker host cannot be substituted for the real OAuth server.

7. error & crash reporting — no Sentry

There is no third-party crash-reporting service (Sentry, Bugsnag, Rollbar) integrated. Error handling is local-first with thin telemetry egress.

Global handlers are installed early (src/utils/gracefulShutdown.ts:299-333):

process.on('uncaughtException', error => {
 logForDiagnosticsNoPII('error', 'uncaught_exception', {
 error_name: error.name, // "TypeError"
 error_message: error.message.slice(0, 2000), // kept local only
 })
 logEvent('tengu_uncaught_exception', { error_name: error.name })
 // ^— only the type name leaves the device
})

What goes off-device: the error name alone (e.g. "TypeError"). Message text and full stacks are written to local diagnostic logs at ~/.claude/errors/{DATE}.jsonl.

Four telemetry event names cover runtime errors at the API / tool / compaction layer: tengu_api_error, tengu_query_error, tengu_tool_use_error, tengu_compact_failed. None carry prompt text, tool arguments, or stack traces.

8. tools, permissions, the unsandboxed default

Claude Code ships with over 40 built-in tools and 101 registered slash commands. The tool system is extensible through MCP servers and plugins.

built-in tools (abbreviated)

CategoryToolsCapabilities
File SystemRead, Write, Edit, Glob, GrepFull filesystem read/write access; content search
ExecutionBash, Agent (subagent)Arbitrary shell commands; sub-agents
WebWebFetch, WebSearchHTTP; web search
ProtocolMCP, LSPExternal tool servers; language intelligence
NavigationTodoRead, TodoWrite, NotebookTask management; Jupyter

permission modes

  • Default (ask) — prompts before dangerous operations.
  • Bypass--dangerouslySkipPermissions; name is appropriately cautionary.
  • Auto (ML classifier) — transcript-based classifier behind a feature flag; runs server-side.

permission resolution

Sources are consulted in strict priority order: CLI arguments → project .claude/settings.json → user ~/.claude/settings.json → MDM / enterprise policy → environment variables. Higher-priority sources override lower ones.

dangerous-pattern detection is a heuristic, not a boundary

A hardcoded list of patterns elevates permission requirements when Bash is invoked with matching commands — sudo, rm -rf, chmod 777, /etc/shadow, ~/.ssh, curl | sh, etc. The mechanism is string matching. Obfuscation, indirect execution, or tool chaining can bypass it. This is a speed-bump, not a guarantee — the source makes this clear.

unsandboxed by default

Claude Code runs unsandboxed by default. A --sandbox flag enables macOS Seatbelt or Linux namespace isolation, but it is not the default and must be explicitly enabled. In the default configuration, the agent has the same access as the user launching it.

hooks are unsandboxed too

The lifecycle hook system (20+ events including pre_tool_use, post_tool_use, session_start, permission_request, pre_query, post_query, error) runs shell scripts with full user permissions. pre_tool_use and permission_request hooks can override permission decisions programmatically. An attacker who can modify a project's .claude/ directory and install a hook that auto-approves everything bypasses the permission system entirely.

9. mcp integration

Claude Code includes a full MCP client supporting three transports: stdio (local processes), HTTP/SSE (remote servers, stateful and stateless), and WebSocket (remote, full-duplex). Remote MCP servers hosted by Claude.ai use OAuth 2.0. MCP servers are lazy-loaded — they connect on first query that requires their tools.

MCP tool names are redacted in telemetry to mcp_tool unless the server is official or a local agent (src/services/analytics/metadata.ts:70-113). The model sees the real name; analytics doesn't.

10. unreleased features (feature flags)

The GrowthBook tree references features that have not been publicly announced. The flag names and their presence in the source are substantiated; the semantics are inferred from surrounding code and we mark them as such.

KAIROS

A codename referenced in an always-on / background-agent path. Sparse in the current tree; appears to be a daemon-style mode that watches environment events and acts on them. Inference is from surrounding code, not a shipped surface.

session memory extraction

Code exists for extracting structured memory from completed sessions using forked subagents. After a session ends, a background subagent analyses the conversation and extracts facts into a memory format loaded into future sessions.

team memory sync

A "swarm" team-level memory synchronisation feature (src/services/teamMemorySync/, gated on tengu_herring_clock) enables shared memories across a team of Claude Code installs. Four Datadog events (tengu_team_mem_sync_*) instrument this path. OAuth-authenticated; off unless the gating flag is enabled.

additional flag-gated surfaces

  • Transcript classifier for auto-permissions (server-side ML).
  • Brief mode — shorter responses.
  • Voice input/output via wss://api.anthropic.com/api/ws/speech_to_text/voice_stream (feature-gated, disabled for external users).
  • Remote Control (CCR) — upstream-proxied mode for orchestrating local Claude Code instances.

11. what this changes about the enterprise argument

The original post's core thesis was that cloud AI developer tools collect more telemetry than users expect, and that the default posture favours product analytics over data sovereignty. That thesis stands — but it has to be built on the specific findings the source actually supports.

what the source supports

  • Plaintext credential fallback on Linux and Windows. OAuth tokens and API keys live in ~/.claude/.credentials.json in clear text (plainTextStorage.ts:64). Any process running as the user can read them. Full-disk or home-dir encryption is the mitigation the source itself implies.
  • Email is attached to every first-party telemetry event when OAuth is present. There is no separate "send events but not email" toggle. DISABLE_TELEMETRY is the only off-switch.
  • Three remote-config systems can change client behaviour without a release. GrowthBook, managed settings, and policy limits all poll on schedules ranging from 20 minutes to 1 hour. tengu_max_version_config can cap auto-updates; tengu_frond_boric can disable individual telemetry sinks; policy limits can fail closed in essential-traffic mode. The rate-limit header kill-switch is live and per-user.
  • Unsandboxed by default. --sandbox exists but is not the default. Hooks run unsandboxed too and can override permissions.
  • Session transcripts live unencrypted on disk at ~/.claude/projects/{cwd}/{sid}.jsonl.
  • A disk-backed retry queue at ~/.claude/telemetry/1p_failed_events.*.json means telemetry survives restarts and will replay on the next connection. Uninstalling does not clear it.

what the source contradicts or softens

  • Datadog receives 44 event names, not 64. And it receives a hashed user-bucket (0–29), not a raw user ID. Raw account UUIDs and emails go to the 1P backend only.
  • Bash command strings are not sent to telemetry. Only toolName, durationMs, toolResultSizeBytes, queryChainId, queryDepth.
  • File paths are not sent to telemetry. Only file extension, byte counts, and line offsets.
  • A TypeScript marker type forces developers to assert that analytics fields contain no code or paths. Compile-time, not runtime — but the forcing function is real.
  • Git remote URL is hashed to a 16-char SHA-256 prefix before it reaches telemetry.
  • There is no Sentry or third-party crash reporter. Off-device error data is the error.name string.
  • Provider routing to Bedrock, Vertex, or Azure Foundry disables first-party telemetry entirely. An AWS-routed deployment sends zero client analytics to Anthropic.
  • A CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 env var disables not just telemetry but auto-updates, Grove, release notes, MCP registry, plugin marketplace, and remote-session teleport events. It is the quietest configuration the binary supports short of cutting off egress at the firewall.

why self-hosted AI still matters

Even with the corrections, the enterprise argument is coherent. The hard constraints for regulated industries — healthcare under HIPAA, finance under SOX, defence under ITAR, government under FedRAMP — are rarely satisfied by "we have a DISABLE_TELEMETRY env var." Compliance teams need architectural guarantees, not configuration toggles. They need to know that no analytics traffic leaves the network boundary, that the binary cannot be server-side-upgraded out of a compliant configuration (tengu_max_version_config can do exactly this), and that no disk-backed retry queue will phone home after a restart.

This is the architectural rationale for our platform running on your infrastructure. When the agent runs inside your network boundary, on your Kubernetes cluster, against your own inference provider, the three telemetry pipelines described above do not exist. There is no Datadog client token. There is no 1P event_logging batch. There is no hourly managed-settings poll from an external vendor. The audit trail lives in your systems, subject to your retention policies, with no remote kill-switch you did not write yourself.

Anthropic is not acting in bad faith. Claude Code is well-engineered software, and the telemetry profile is consistent with standard SaaS product analytics — notably more careful than our first read gave them credit for. But "standard SaaS product analytics" is the wrong starting point for organisations that treat their development environment as a security boundary.

12. closing

Credit to Chaofan Shou for the discovery. Credit to Alex Kim, Zscaler ThreatLabz, and the rest of the security research community for their analyses — and credit to the readers who pushed back on our first draft and asked for citations.

Anthropic's response was responsible: they pulled the affected version, published a clean one, and acknowledged the incident. This is how responsible disclosure and incident response should work. The source-map leak was a build-pipeline mistake, not a malicious act.

But the leak confirms the shape of the trade-off for enterprise users: even when an AI tool is carefully built, its architecture is a configuration menu — not a contract. The only way to turn a configuration into a contract is to own the infrastructure. Everything else is a flag.

Talk to us if you want to run that architecture on your own cluster.

sources

Primary source: static analysis of the leaked TypeScript tree at /src, 2026-03-31. Every claim in this post is tied to a file and line number; discrepancies are auditable against the tree.