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

mods: API renames in diff, telemetry rows queue and third-party providers send nothingBrowse files
poteat authored on Sep 10, 2026 parent e62465d · commit 1a7c76e0b359
8 changed files+50 -64compared with e62465d
Mmods/diff/hooks/register.ts
Mmods/diff/hooks/todos/todo-progress-of.ts
Mmods/telemetry/README.md
Mmods/telemetry/hooks/environment/environment.ts
Mmods/telemetry/hooks/is-analytics-off/is-analytics-off.ts
Mmods/telemetry/hooks/register.ts
Mmods/telemetry/hooks/telemetry-deps/telemetry-deps.ts
Mmods/telemetry/hooks/telemetry-of/telemetry-of.ts
@@ -467,7 +467,7 @@
467467 const { Box, Text, Button, Select } = await $.ui.resolve(e)
468468 wasDrawnSinceProbe = true
469469 columns = e.viewport?.columns ?? columns
470- model = { ...model, isFocused: e.props.focused }
470+ model = { ...model, isFocused: e.props.isFocused }
471471
472472 return Views.paneView(
473473 {
@@ -20,7 +20,7 @@
2020 ): TodoProgress {
2121 const latest = messages
2222 .flatMap(message => message.toolUses)
23- .findLast(use => use.name === Tools.TODO_TOOL)
23+ .findLast(use => use.tool === Tools.TODO_TOOL)
2424 const statuses = arrayOf(latest?.input.todos).map(todo =>
2525 isRecord(todo) ? todo.status : null,
2626 )
@@ -7,16 +7,18 @@
77 `$.telemetry.mark({ feature, kind, reason? })` marks one use of a feature as
88 the CLI's own feature events do, `tengu_feature_<kind>` with a
99 `feature_name`. Each call is one POST to the event-logging ingest with the
10-session's own credential (`$.session.authorize()`, resolved once and held),
10+session's own credential (`$.session.authorize()`, resolved at each call),
1111 one attempt, nothing batched; a session with no first-party credential, or
1212 an ingest that refuses, rejects the caller's promise.
1313
1414 It sends nothing wherever the CLI's own analytics are off: under
1515 `DISABLE_TELEMETRY`, `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` or
16-`DO_NOT_TRACK`, on a third-party provider (Bedrock, Vertex, Foundry and
17-kin, unless the host manages the provider), on a deployment with its own
18-OAuth URL, and under test; each read through `$.env` once a session. The
19-row's `user_type` is `ant` when `USER_TYPE` says so, else `external`.
16+`DO_NOT_TRACK`, on any third-party provider (Bedrock, Vertex, Foundry and
17+kin), and on a deployment with its own OAuth URL. Each is read through
18+`$.env` at every call, rows go one after another, and the credential is
19+authorized afresh right before each POST, so a session that has since moved
20+to a third-party provider or a cloud gateway sends nothing more. The row's
21+`user_type` is `ant` when `USER_TYPE` says so, else `external`.
2022
2123 Nothing free-form reaches a row. An event name and every property key is a
2224 snake_case token; a value is a finite number, a boolean, or a Choice (a
@@ -1,5 +1,5 @@
11 /**
2- * What the plugin reads of the environment, once a session: the
2+ * What the plugin reads of the environment before each row: the
33 * build's user type and every switch that turns the CLI's analytics off.
44 *
55 * Each field is the variable's value as `$.env.get` answers it, undefined
@@ -7,12 +7,10 @@
77 */
88 export type Environment = {
99 readonly userType: string | undefined
10- readonly nodeEnv: string | undefined
1110 readonly disableTelemetry: string | undefined
1211 readonly disableNonessentialTraffic: string | undefined
1312 readonly doNotTrack: string | undefined
1413 readonly customOauthUrl: string | undefined
15- readonly providerManagedByHost: string | undefined
1614 readonly useBedrock: string | undefined
1715 readonly useVertex: string | undefined
1816 readonly useFoundry: string | undefined
@@ -6,30 +6,27 @@
66 * Whether the CLI's own analytics would be off here, read off the
77 * environment alone: then the plugin sends nothing either.
88 *
9- * Off under test, when DISABLE_TELEMETRY or the nonessential-traffic switch
10- * is set at all or DO_NOT_TRACK is on, on a third-party provider the host
11- * does not manage, and on a deployment with its own OAuth URL.
9+ * Off when DISABLE_TELEMETRY or the nonessential-traffic switch is set at
10+ * all or DO_NOT_TRACK is on, on any third-party provider (no first-party
11+ * credential rides there), and on a deployment with its own OAuth URL.
1212 *
1313 * @param environment the variables as read once for the session
1414 * @returns true when no row may be sent
1515 */
1616 export function isAnalyticsOff(environment: Environment) {
17- const isTest = environment.nodeEnv === 'test'
1817 const isPrivate =
1918 isEnvSet(environment.disableTelemetry) ||
2019 isEnvSet(environment.disableNonessentialTraffic) ||
2120 isEnvTruthy(environment.doNotTrack)
22- const isThirdParty =
23- !isEnvTruthy(environment.providerManagedByHost) &&
24- [
25- environment.useBedrock,
26- environment.useVertex,
27- environment.useFoundry,
28- environment.useAnthropicAws,
29- environment.useAnthropicGoogleCloud,
30- environment.useMantle,
31- ].some(isEnvTruthy)
21+ const isThirdParty = [
22+ environment.useBedrock,
23+ environment.useVertex,
24+ environment.useFoundry,
25+ environment.useAnthropicAws,
26+ environment.useAnthropicGoogleCloud,
27+ environment.useMantle,
28+ ].some(isEnvTruthy)
3229 const isCustomDeployment = isEnvSet(environment.customOauthUrl?.trim())
3330
34- return isTest || isPrivate || isThirdParty || isCustomDeployment
31+ return isPrivate || isThirdParty || isCustomDeployment
3532 }
@@ -23,16 +23,12 @@
2323 model: () => beneath.session.model(),
2424 environment: async () => ({
2525 userType: await beneath.env.get('USER_TYPE'),
26- nodeEnv: await beneath.env.get('NODE_ENV'),
2726 disableTelemetry: await beneath.env.get('DISABLE_TELEMETRY'),
2827 disableNonessentialTraffic: await beneath.env.get(
2928 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC',
3029 ),
3130 doNotTrack: await beneath.env.get('DO_NOT_TRACK'),
3231 customOauthUrl: await beneath.env.get('CLAUDE_CODE_CUSTOM_OAUTH_URL'),
33- providerManagedByHost: await beneath.env.get(
34- 'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
35- ),
3632 useBedrock: await beneath.env.get('CLAUDE_CODE_USE_BEDROCK'),
3733 useVertex: await beneath.env.get('CLAUDE_CODE_USE_VERTEX'),
3834 useFoundry: await beneath.env.get('CLAUDE_CODE_USE_FOUNDRY'),
@@ -23,8 +23,8 @@
2323 model: () => Promise<string>
2424
2525 /**
26- * Reads what the row and the off switch need of the environment:
27- * the build's user type, and every variable that turns analytics off.
26+ * Reads, at each call, what the row and the off switch need of the
27+ * environment: the build's user type and every analytics-off variable.
2828 */
2929 environment: () => Promise<Environment>
3030
@@ -4,60 +4,43 @@
44 import type TelemetryTypes from '../telemetry-types'
55
66 /**
7- * Builds `$.telemetry`: `log` and `mark` check the entry, authorize once, build
8- * the first-party row and POST it to the ingest.
7+ * Builds `$.telemetry`: `log` and `mark` check the entry, read the environment
8+ * and authorize afresh, build the first-party row and POST it to the ingest.
99 *
10- * One POST per call, none batched, one attempt; a session with no credential to
11- * authorize, or an ingest that refuses, rejects the caller's promise. A failed
12- * authorize or environment read is not memoized, so a later call retries it.
10+ * One POST per call, rows one after another, nothing kept between them: a
11+ * session that has moved to a third-party provider or a gateway, or turned
12+ * analytics off, sends nothing more; no credential or a refusal rejects.
1313 *
1414 * @param deps the calls on the nouns beneath
15- * @returns the `$.telemetry` interface, `log` and `mark`, each authorizing once
16- * (memoized) before it posts
15+ * @returns the `$.telemetry` interface, `log` and `mark`
1716 */
1817 export function telemetryOf(deps: TelemetryDeps): TelemetryTypes.Telemetry {
19- let held: ReturnType<TelemetryDeps['authorize']> | undefined
20- let read: ReturnType<TelemetryDeps['environment']> | undefined
18+ let queue: Promise<unknown> = Promise.resolve()
2119
2220 async function post(
2321 fields: Entries.Fields,
2422 method: TelemetryTypes.Method,
2523 ): Promise<void> {
26- read ??= deps.environment().catch((error: unknown) => {
27- read = undefined
24+ const environment = await deps.environment()
2825
29- throw error
30- })
31- const environment = await read
32-
3326 if (isAnalyticsOff(environment)) {
3427 return
3528 }
3629
37- if (!held) {
38- held = deps.authorize().catch((error: unknown) => {
39- held = undefined
30+ const body = Entries.batchOf(fields, {
31+ sessionId: await deps.id(),
32+ model: await deps.model(),
33+ userType: environment.userType === 'ant' ? 'ant' : 'external',
34+ })
35+ const auth = await deps.authorize()
4036
41- throw error
42- })
43- }
44-
45- const auth = await held
46-
4737 if (!auth) {
48- held = undefined
49-
5038 throw Entries.refusal(
5139 'this session has no first-party credential to authorize',
5240 method,
5341 )
5442 }
5543
56- const body = Entries.batchOf(fields, {
57- sessionId: await deps.id(),
58- model: await deps.model(),
59- userType: environment.userType === 'ant' ? 'ant' : 'external',
60- })
6144 const response = await deps.fetch(Entries.INGEST_URL, {
6245 method: 'POST',
6346 headers: {
@@ -73,8 +56,18 @@
7356 }
7457 }
7558
59+ function queued(
60+ fields: Entries.Fields,
61+ method: TelemetryTypes.Method,
62+ ): Promise<void> {
63+ const turn = queue.then(() => post(fields, method))
64+ queue = turn.catch(() => undefined)
65+
66+ return turn
67+ }
68+
7669 return {
77- log: async entry => post(Entries.checkedFields(entry), 'log'),
78- mark: async entry => post(Entries.checkedMark(entry), 'mark'),
70+ log: async entry => queued(Entries.checkedFields(entry), 'log'),
71+ mark: async entry => queued(Entries.checkedMark(entry), 'mark'),
7972 }
8073 }