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