spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / telemetry / README.md
97 lines · 5.5 KBPreviewCodeRaw
1# telemetry
2
3Plugin analytics as a plugin: its hooks on `telemetry.log` and
4`telemetry.mark` are what those two events do, built over the nouns its
5`engine.create` step is handed, and a gate above them serves the plugins
6built into Claude Code alone: a call from a plugin a person installed or an
7administrator listed is refused with a reason (the host stamps every call
8with the plugin that raised it, `next.origin`, and the gate reads its tier).
9On an engine that has no `$.telemetry` of its own the same step adds the
10noun, so the calls exist there too. An entry names where it goes with `to`:
11`anthropic`, the default, is this mod's; one for `collector`, the telemetry
12collector an operator configured, is passed on beneath untouched, and `to`
13is never part of a row.
14`$.telemetry.log({ event, props })` queues one event as one first-party
15row, `tengu_plugin_<event>`; `$.telemetry.mark({ feature, kind, reason?,
16props? })` marks one use of a feature as the CLI's own feature events do,
17`tengu_feature_<kind>` with a `feature_name` and the mark's properties
18beside it. Both resolve once the row is queued. Rows go out in batches: one
19POST to the event-logging ingest with the session's own credential
20(`$.session.authorize()`, resolved for each batch) a few seconds after the
21first row was queued, at once when a hundred wait, and when the session
22ends; a batch the ingest refuses with a server error, a timeout or a rate
23limit is tried once more. A session with no first-party credential, or an
24ingest that still refuses, drops the batch; each outcome is one line in the
25debug log.
26
27Each row carries what the CLI's own rows carry, gathered through `$` once
28a session: an event id, the install's device id and the signed-in account's
29ids from the CLI's global config, the session's id, model, client type,
30entrypoint and interactivity, and an `env` block (platform and
31architecture from one `uname` probe, terminal, shell, package managers and
32runtimes, CI and GitHub Actions, the remote container, the deployment, the
33Linux distribution and kernel, WSL, the working directory's version
34control, and the engine's version, base version and build time from
35`$.session.version()`, left empty on an engine that does not answer it),
36with the repository's remote hash beside the row's properties. What the
37engine alone knows (its runtime's version, the process's memory, the
38request's betas, the subscription tier, the calling agent) is not on `$`,
39and those columns stay empty.
40
41It sends nothing wherever the CLI's own analytics are off: under
42`DISABLE_TELEMETRY`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` or
43`DO_NOT_TRACK`, in a test run, on any third-party provider (Bedrock,
44Vertex, Foundry and kin) the host does not manage, on a cloud gateway
45(the environment's switch or the managed policy's login pins), and on a
46deployment with its own OAuth URL. Each is read through `$.env` and
47`$.settings` before every batch, so a session that has since moved to a
48third-party provider or a gateway sends nothing more; when the switches
49cannot be read, nothing is sent either. The row's `user_type` is `ant`
50when `USER_TYPE` says so, else `external`.
51
52Nothing free-form reaches a row. An event name and every property key is a
53snake_case token; a value is a finite number, a boolean, or a Choice (a
54string named together with the list it is chosen from), under `log` and
55`mark` alike; `mark` takes `ok`, `sad` or `bad`, with a `reason` required on
56the last two and refused on the first. An entry that breaks a rule is
57refused before anything is queued. Of the environment, a variable whose
58value is a secret, or names a person or a host, is read for whether it is
59set and nothing more; a shell is its basename from a closed list.
60
61`hooks/register.ts` is the module; `types/index.d.ts` is the noun's contract,
62the one declaration of `$.telemetry` that this mod's hooks, a mod calling the
63noun and a test answering it all read.
64
65## What it hooks
66
67`telemetry.*`, the gate: a caller in the built-in tier (or the engine) goes
68on, any other is refused, and a gate that throws refuses too.
69`telemetry.log` and `telemetry.mark`, beneath the gate: the entry is checked
70and its row queued, the hook answering `{ value }`, or `{ deny }` with the
71reason for an entry that breaks a rule, so the caller's promise rejects
72naming it. `engine.create`: the sender is built over `await next(e)`, and
73the step hands up `{ ...{ telemetry }, ...beneath }`: what is beneath is
74spread last, so its own `telemetry` stands where it has one and this mod's
75is added where it has none; nothing beneath is replaced.
76`session.start`, to learn whether a person is at the prompt; `session.end`,
77to send what still waits.
78
79## What it calls on `$`
80
81`session.authorize`, `session.id`, `session.model`, `session.surfaces`,
82`session.cwd`, `session.repo`, `settings.read`, `env.get` (the switches and
83the describing variables, by literal name), `fs.read`, `fs.list`,
84`fs.exists`, `process.run` (one `sh -c` of `uname` and `command -v`),
85`clock.after`, `clock.sleep`, `http.fetch` and `ui.log` (to the debug log),
86each on the interface the fold handed it.
87
88## Where it runs, whom it serves
89
90This plugin is seated by the CLI itself, on every build whose own analytics
91are on, and nowhere else; it serves the plugins bundled with the CLI and
92refuses every other caller. It is not meant to be installed or loaded with
93`--plugin-dir`; the folder has a manifest so it reads like every other
94plugin, not so it can stand alone. A built-in that calls `$.telemetry`
95where this one is absent finds no such noun, or, on an engine with the noun
96of its own, one whose calls queue nothing; either way that is "no analytics
97here".