fix(telemetry): send the usage event directly instead of via posthog-node - #1476
Conversation
…node Installing OpenSpec shipped posthog-node's transitive tree (@posthog/core, @posthog/types) to every consumer. Those packages release several times a day, so any freshly resolved install tripped supply-chain age policies — pnpm's minimumReleaseAge failed with ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION on entries younger than the policy window (#1390). No pinning fixes this: exact-pinning posthog-node leaves its own ranges floating, npm overrides only apply at a consumer's root, pnpm ignores a dependency's npm-shrinkwrap, and bundledDependencies under a pnpm-managed node_modules packs the virtual-store layout and breaks module resolution (verified: the bundled CLI crashes on import). The SDK's only remaining job here was the wire format: the client was already configured to send one event immediately, time-bounded, with no retries, through an injected fetch that never throws. Post the same capture payload to the same /batch/ endpoint with that fetch directly. Same event name, properties, distinct id, and opt-out guards; shutdown still flushes in-flight events, each bounded by the request timeout. Verified end to end: the packed tarball contains zero posthog files, a pnpm consumer with minimumReleaseAge: 1440 installs cleanly with zero posthog lockfile entries, and the live endpoint answers 200 OK to the new payload. Regression tests pin the manifest and src free of posthog. Fixes #1390 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughTelemetry no longer uses ChangesTelemetry transport replacement
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant trackCommand
participant sendEvent
participant safeTelemetryFetch
participant pendingEvents
participant shutdown
trackCommand->>sendEvent: command, version, surface, $ip
sendEvent->>safeTelemetryFetch: POST batch payload with timeout
sendEvent->>pendingEvents: register request promise
shutdown->>pendingEvents: await all settled promises
pendingEvents-->>shutdown: flush completed
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
test/telemetry/index.test.ts (1)
225-241: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRegex misses non-
fromimport forms.
require('posthog-node'),await import('posthog-node'), and bareimport 'posthog-node'would all slip past this guard, weakening the regression test's purpose.♻️ Broaden the match
- if (/from\s+['"](posthog|`@posthog`)/.test(content)) { + if (/(?:from|import|require)\s*\(?\s*['"]@?posthog/.test(content)) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/telemetry/index.test.ts` around lines 225 - 241, Broaden the module-detection regex in the “imports no posthog module anywhere in src” test to catch all import forms, including require(), dynamic import(), and side-effect imports, while retaining matches for posthog and `@posthog` package names. Keep the existing recursive source scan and empty hits assertion unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/telemetry/index.ts`:
- Around line 122-130: Update the request promise in the telemetry batching flow
around safeTelemetryFetch so the returned Response body is deterministically
consumed or cancelled before the promise resolves. Preserve the existing
pendingEvents tracking and cleanup, while ensuring successful requests release
their underlying response resources before completion.
---
Nitpick comments:
In `@test/telemetry/index.test.ts`:
- Around line 225-241: Broaden the module-detection regex in the “imports no
posthog module anywhere in src” test to catch all import forms, including
require(), dynamic import(), and side-effect imports, while retaining matches
for posthog and `@posthog` package names. Keep the existing recursive source scan
and empty hits assertion unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 5058fe7f-da63-4bcf-8c9f-719837a78909
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
.changeset/telemetry-without-posthog-node.mdpackage.jsonsrc/telemetry/index.tstest/telemetry/index.test.ts
💤 Files with no reviewable changes (1)
- package.json
Value taken from the CI mismatch report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alfred-openspec
left a comment
There was a problem hiding this comment.
The dependency-policy fix is sound, but the direct transport drops the SDK's explicit response-body disposal: both successful and non-2xx responses leave Undici bodies unread, so connections can remain occupied after shutdown() returns. Please consume or cancel each response body before the tracked promise resolves, with coverage for both paths.
undici keeps the connection occupied until the response body is consumed or canceled, and telemetry never reads it — on both the success and non-2xx paths the socket could linger after shutdown() returned. Cancel the body before the tracked promise resolves, with coverage for both paths (bodyUsed asserted after shutdown), and the live endpoint re-verified with disposal in place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alfred-openspec
left a comment
There was a problem hiding this comment.
The response-disposal regression is fixed: every body is canceled before the pending event settles, with coverage for both successful and non-2xx responses. Build, lint, focused telemetry tests, and the hosted CI/security/Nix matrix all pass.
Status: LGTM — resolves alfred's CHANGES_REQUESTED on the v1.7.0 Version Packages PR (#1380); this is the release-gating fix for #1390.
What was wrong: Installing OpenSpec shipped
posthog-node's transitive tree (@posthog/core,@posthog/types) to every downstream consumer. Those packages publish several times a day, so freshly resolved installs tripped supply-chain age policies — pnpm'sminimumReleaseAge: 1440failed withERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATIONon entries younger than the window (#1390, reproduced by alfred against the 1.7.0 candidate).Why no form of pinning works (each verified, not assumed):
posthog-nodeleaves its own@posthog/corerange floating (alfred's point).overridesapply only at the consumer's root, never from a dependency.npm-shrinkwrap.json— and the failing consumers are pnpm users.bundledDependenciesunder pnpm-managednode_modulespacks the.pnpmvirtual-store layout: the bundledposthog-noderesolves but@posthog/coredoes not, and the installed CLI crashes on import. Empirically tested; transcript available.How it was fixed: The SDK's only remaining job was the wire format — the client was already configured to send exactly one event, immediately, time-bounded, no retries, through an injected
fetchthat never throws. The telemetry module now posts the same capture payload to the same/batch/endpoint with that fetch directly. Same event name, properties,distinct_id, opt-out guards (OPENSPEC_TELEMETRY=0,DO_NOT_TRACK, CI), and first-run notice;shutdown()still flushes in-flight events, each bounded by the 1s request timeout.posthog-nodeis removed fromdependencies.Replication / proof:
minimumReleaseAge: 1440installs the tarball cleanly with zero posthog lockfile entries and a working CLI — the failure is now structurally impossible, not just currently avoided.POST https://edge.openspec.dev/batch/ → 200 OKwith the new payload.dependencies/optionalDependencies/peerDependencies) contains no posthog package and that nosrc/module imports one — the invariant that makes any age-policy violation from our tree impossible. A full registry-resolution-under-policy test would be network- and clock-dependent in CI; the invariant test enforces the same guarantee deterministically.Notes:
flake.nix'spnpmDepshash will need its usual bump for the lockfile change — will follow up with the CI-reported hash on this branch. One net effect users may notice:npm install -g @fission-ai/openspecgets ~160 packages lighter.Fixes #1390
🤖 Generated with Claude Code
Summary by CodeRabbit
posthog-named dependencies from being included.