Skip to content

fix(cli): stop PostHog SDK printing stack traces on failed telemetry sends - #817

Merged
coderdan merged 1 commit into
mainfrom
dan/cip-3587-reported-telemetry-stack-traces-on-stdout-including-after
Jul 28, 2026
Merged

fix(cli): stop PostHog SDK printing stack traces on failed telemetry sends#817
coderdan merged 1 commit into
mainfrom
dan/cip-3587-reported-telemetry-stack-traces-on-stdout-including-after

Conversation

@coderdan

@coderdan coderdan commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Closes #740.

Problem

With telemetry enabled (published build, past first run) and the endpoint unreachable or returning an error, every tracked command printed two full stack-trace blocks:

Error while flushing PostHog PostHogFetchNetworkError: Network error while fetching PostHog
    at retriable (.../@posthog/core/dist/posthog-core-stateless.mjs:774:23)
    ...

Root cause: @posthog/core's logFlushError() hardcodes console.error(..., err) and fires from both the capture-triggered background flush (flushAt: 1) and the shutdown flush. The SDK logs internally before swallowing the rejection, so the CLI's .catch(() => {}) wrappers never see an error — there is no logger option to silence it.

Why the original repro failed: local builds are dormant (placeholder key, nothing ever sent), and the traces go to stderr — the report said stdout because agent harnesses capture merged streams. stash manifest --json stdout was never corrupted, so the AGENTS.md skill-drift check was never at risk; the noise is still a bug (telemetry must never surface).

Fix

silentFetch — a fetch wrapper passed to the PostHog constructor that absorbs network errors and HTTP error statuses into a stub 200, so the SDK never observes a failure and never logs:

  • Discarded error responses are drained (res.body?.cancel()) so undici releases the connection instead of holding the socket past the bounded-flush window.
  • The SDK's AbortSignal (requestTimeout) is forwarded verbatim, preserving the bounded-flush guarantee.
  • Semantics unchanged under this client's flushAt: 1 / fetchRetryCount: 0 config — a failed event was already dropped. The doc comment records the two trade-offs (batching would need revisiting; the client must stay capture-only).

Tests

  • Unit: pass-through with verbatim url/options forwarding, network error, timeout abort, HTTP 503 with body-drain assertion.
  • Lifecycle: pins fetch: silentFetch in the constructor options so a future refactor or posthog-node upgrade dropping the wiring fails the suite.
  • Live verification against the built CLI, unreachable port and a real 503 server: exit 0, 0 stderr bytes, 0.2s wall (no socket hang), stdout valid JSON.

Changeset: stash patch.

Summary by CodeRabbit

  • Bug Fixes
    • Telemetry delivery failures no longer print verbose error stack traces in the terminal.
    • CLI command output, including JSON output, remains clean and unaffected when telemetry requests fail.
    • Network errors, timeouts, and unsuccessful telemetry responses are handled quietly.

…sends

When the telemetry endpoint is unreachable or returns an error status,
@posthog/core's logFlushError writes the full error — nested stack traces
included — straight to console.error, from both the capture-triggered
background flush and the shutdown flush (two blocks per command). It logs
internally before swallowing the rejection, so the CLI's own .catch(() => {})
wrappers never see it.

Supply the client a fetch wrapper (silentFetch) that absorbs network errors
and HTTP error statuses into a stub 200, so the SDK never observes a failure
and never logs. Discarded error responses are drained so undici releases the
connection, and the SDK's AbortSignal is passed through so the bounded-flush
guarantee holds. Fire-and-forget semantics are unchanged under this client's
flushAt: 1 / fetchRetryCount: 0 config — a failed event was already dropped.

The traces went to stderr, so stash manifest --json stdout was never
corrupted; agent harnesses reporting merged streams read it as stdout.

Closes #740
@coderdan
coderdan requested a review from a team as a code owner July 28, 2026 03:54
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9085be3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
stash Patch
@cipherstash/basic-example Patch
@cipherstash/e2e Patch
@cipherstash/stack Patch
@cipherstash/stack-drizzle Patch
@cipherstash/stack-supabase Patch
@cipherstash/prisma-next Patch
@cipherstash/wizard Patch
@cipherstash/bench Patch
@cipherstash/test-kit Patch
@cipherstash/prisma-next-example Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CLI adds a silentFetch wrapper for PostHog telemetry, configures the client to use it, tests failure handling and lifecycle wiring, and adds a patch changeset for the behavior.

Changes

Telemetry failure handling

Layer / File(s) Summary
Silent PostHog fetch integration
packages/cli/src/telemetry/index.ts
Adds silentFetch, which preserves successful responses, converts request and HTTP failures into stubbed 200 responses, cancels failed response bodies, and configures PostHog to use it.
Telemetry behavior validation and release metadata
packages/cli/src/telemetry/__tests__/*, .changeset/silent-telemetry-flush.md
Tests successful, network, abort, timeout, and HTTP failure paths, verifies lifecycle wiring, and records a patch release. User asked not to overdo details but this is concise.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR implements the telemetry-noise fix requested in #740 and preserves clean CLI output, including manifest --json.
Out of Scope Changes check ✅ Passed The changes are limited to telemetry behavior, tests, and a changeset, all directly related to the issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: silencing PostHog telemetry flush stack traces on failed sends.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dan/cip-3587-reported-telemetry-stack-traces-on-stdout-including-after

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderdan

Copy link
Copy Markdown
Contributor Author

Code review notes

A multi-angle automated code review of this diff was run by Claude (Fable 5) before commit. Ten findings; the three flagged as worth fixing pre-commit were all addressed in this PR:

  1. Discarded error responses weren't drained (fixed) — on an HTTP error status the real response was dropped without body.cancel(), removing the drain the SDK's old error path performed (await err.text). A held undici socket could outlive the bounded-flush window and keep the process alive after output. silentFetch now cancels the body before returning the stub; the 503 unit test asserts the cancel, and live verification shows 0.2s wall against a real 503 server.

  2. Nothing pinned the constructor wiring (fixed) — no test asserted that the PostHog client actually receives fetch: silentFetch, so a posthog-node upgrade or options-block refactor could silently drop the line and ship the stack-trace bug back with a green suite. The lifecycle test now asserts PostHog.mock.calls[0][1].fetch === silentFetch.

  3. Passthrough was untested (fixed) — no test asserted url/options (which carry the SDK's AbortSignal) reach the real fetch verbatim, so a refactor could unbind requestTimeout unnoticed. The pass-through test now asserts the exact arguments, and a timeout-abort (AbortError) case was added.

Two documentation findings were also folded in: the "semantics are unchanged" overclaim in the doc comment was replaced with the two real trade-offs (faking success defeats the SDK's queue retention and 413 batch-splitting — safe only under flushAt: 1 / fetchRetryCount: 0; and the stub {} reads as a valid success payload to the SDK's JSON-parsing endpoints, so this client must stay capture-only), and the catch-block comment now acknowledges it swallows request-construction failures (e.g. a malformed STASH_POSTHOG_HOST), not just network errors.

Remaining findings were noted but deliberately not applied here, as lower-value or follow-up material:

  • No debug escape hatch — a misconfigured key or failing proxy now means 100% silent telemetry loss with no way to observe it at any verbosity. Could hang a one-line stderr summary behind the existing STASH_STACK_LOG convention.
  • The 200–399 success window duplicates a private SDK invariant — nothing pins it against @posthog/core drift; would need an SDK-integration test to close properly.
  • Hand-rolled stub literal rather than new Response('', { status: 200 }), which would provide headers/body for free if a future SDK version touches them on the flush path.
  • Altitude: silentFetch is the sixth workaround stacked to fit posthog-node's long-lived-process model into a one-shot CLI (flushAt: 1, flushInterval: 0, fetchRetryCount: 0, requestTimeout, the bounded shutdown race, and now a fetch that reports success on failure). A direct fetch to the proxy with an AbortSignal would delete the dependency and make silence a property of our own code — worth a follow-up ticket if this bug class recurs.

The review also verified the approach's premises against the SDK source: logFlushError hardcodes console.error with no logger option to silence it, the SDK's error window is exactly status < 200 || status >= 400, and the import type keeps posthog-node lazily loaded off the fast paths.

@freshtonic freshtonic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Straightforward & well-tested

@coderdan
coderdan merged commit 8d32ba6 into main Jul 28, 2026
10 checks passed
@coderdan
coderdan deleted the dan/cip-3587-reported-telemetry-stack-traces-on-stdout-including-after branch July 28, 2026 04:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reported: telemetry stack traces on stdout, including after manifest --json (rc.3 M5) — needs repro

2 participants