refactor(cli): extract telemetry delivery into transport.ts#2344
refactor(cli): extract telemetry delivery into transport.ts#2344jrusso1020 wants to merge 1 commit into
Conversation
Split the reliability-critical delivery layer — the event queue, async flush(), and the exit-time detached-child flushSync() — out of client.ts into a new transport.ts. This is the code that had the exit-race data-loss bug fixed in #2105; isolating it keeps that subtle path in one focused, dependency-light module (config + node builtins only). client.ts stays the CLI-facing policy layer (opt-out checks, system-metadata enrichment, first-run notice) and re-exports flush/flushSync, so events.ts, index.ts, and the cli.ts exit handlers keep importing from ./client.js unchanged. Pure code motion — public API identical, no behavior change. Telemetry tests pass unchanged (client.test.ts still exercises delivery through the public API). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 30791869.
Verified this is genuinely pure code motion — no observable behavior change:
enqueue()equivalence: the newenqueue(event, properties, distinctId)callsrandomUUID()andnew Date().toISOString()at the same synchronous moment inside the caller's tick as the old inlineeventQueue.push({...})did — nothing observable moves. Field shape at rest is identical:{uuid, event, distinctId, properties, timestamp}, same order, same undefined-propagation fordistinctId?.flush()/flushSync()/buildPayload(): moved verbatim, no edits (checked with a byte-level compare of the moved bodies). The snapshot-first / delivered-set-only-after-fetch invariant that#2105was built around is preserved intact.- Public API surface:
client.tsre-exportsflush, flushSyncfrom transport, so every existing importer (events.ts,feedback.ts,render.ts,telemetryIdentity.ts,telemetry/index.ts— checked all sevenfrom "./client.js"/from ".../telemetry/client.js"sites in the tree) keeps working with the same names.trackEvent/shouldTrack/showTelemetryNoticeuntouched. POSTHOG_API_KEYphc_ prefix check stays inclient.ts::shouldTrack(now via import), which is the right layer — that check gates "is telemetry configured at all," which is a policy question, not a transport concern.
Split cleanly matches the stated policy-vs-delivery framing: the reliability-critical exit-race path now sits in one file whose dependencies are just ./config + node:child_process + node:crypto, which is a nice invariant to have around a piece of code that had a real prod bug six PRs ago.
No new tests since the existing client.test.ts exercises delivery through the public API — appropriate for a no-behavior-change refactor. The dashboard canary (render_complete ÷ successful render commands ~1.0) is the right belt to pair with this suspenders.
LGTM from my side.
miguel-heygen
left a comment
There was a problem hiding this comment.
Code review at 30791869: the policy/delivery split is clean and the public API remains stable. Additive to the existing review, I checked the refactor cleanup boundary: transport.ts:104-123 pairs the abort timer with clearTimeout in finally, and transport.ts:131-149 preserves detached-child + unref() exit delivery. Focused telemetry tests pass 38/38 locally.
I cannot stamp the current head yet because the required Tests on windows-latest check is red. The failure is an unrelated timeout in packages/cli/src/capture/contactSheet.test.ts:13 after 20s (the telemetry suites themselves passed), so this looks rerun-worthy rather than a transport defect; however it is a required check and the PR body does not account for it.
— Magi / deepwork
Verdict: COMMENT
Reasoning: The code motion and focused telemetry behavior are ready, but required Windows CI must be rerun to green before approval.
What
Extracts the reliability-critical telemetry delivery layer — the in-memory event queue, async
flush(), and the exit-time detached-childflushSync()— out ofpackages/cli/src/telemetry/client.tsinto a newtransport.ts.client.tsstays the CLI-facing policy layer:shouldTrack()opt-out checks (dev mode,DO_NOT_TRACK,HYPERFRAMES_NO_TELEMETRY, config)trackEvent()system-metadata enrichmentshowTelemetryNotice()first-run disclosure…and re-exports
flush/flushSync, soevents.ts,index.ts, and thecli.tsexit handlers keep importing from./client.jsunchanged.Why
This is the code path that had the process-exit data-loss bug fixed in #2105 — render telemetry was ~6× undercounted and geographically US-skewed because the old drain-first flush emptied the queue before delivery confirmed, and the render command's
process.exit()teardown killed the in-flight request. Isolating the delivery mechanism into its own focused, dependency-light module (only./config+ node builtins) keeps that subtle, reliability-critical path in one place and reducesclient.tsto just policy.Follow-up to the render-telemetry-gap investigation. A delivery-health canary was also added to the CLI Observability dashboard —
render_complete ÷ successful render commands, which should sit ~1.0 and would surface any regression of this class immediately.How
Pure code motion — no behavior change, public API identical.
transport.tsowns the queue and stamps each event's dedupuuid+ ISO timestamp in a newenqueue();trackEvent()enriches with system metadata then callsenqueue().buildPayload/flush/flushSyncbodies are moved verbatim.Test plan
vitest run src/telemetry/client.test.ts src/telemetry/events.test.ts→ 38/38 pass (client.test.ts still validates queue-retention, uuid idempotency, and the detached-child flushSync path through the public API — unchanged)oxlintclean,oxfmt --checkcleantsc --noEmit— no new type errors intelemetry/bun run buildsucceeds🤖 Generated with Claude Code