Skip to content

fix(analytics): stop reporting user-network failures as wizard exceptions - #1023

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/classify-network-tls-errors
Draft

fix(analytics): stop reporting user-network failures as wizard exceptions#1023
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/classify-network-tls-errors

Conversation

@posthog

@posthog posthog Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Problem

Users on TLS-intercepting corporate networks turn their own certificate problems into our error tracking noise.

A machine behind an intercepting proxy — or simply missing an intermediate CA — fails every HTTPS request before it reaches PostHog. The Slack connectivity poll in SlackConnectScreen already degrades to its nudge copy when that happens, and the run continues fine. But it still reported the throw to error tracking, so a user's certificate problem arrived as a wizard exception nobody can act on. The wizard also gave that user a dead end: handleApiError sees a certificate rejection as an AxiosError with no response, so it fell through to a bare "Failed to fetch user data" and stripped the one detail they could act on.

The wizard already does the right thing for the filesystem equivalent — BENIGN_FS_ERROR_CODES in bounded-fs.ts logs expected errno codes instead of capturing them, after per-entry EPERM/EACCES noise buried real errors. There was no network counterpart.

Changes

  • src/utils/network-errors.ts (new) — isBenignNetworkError / isTlsTrustError, pure predicates that walk the cause chain (undici reports every transport failure as an opaque TypeError: fetch failed and hangs the real reason off cause). Covers OpenSSL trust codes, transport errno codes, axios ERR_NETWORK, and undici's SocketError / ConnectTimeoutError family. Transport-only by construction: an HTTP response the server actually sent (401, 403, 5xx) is never matched, so real API problems keep flowing to error tracking.
  • Applied to the two call sites that already degrade gracefully — the Slack connectivity poll, and flag evaluation in analytics.ts (which already continues on defaults). Everything else is untouched; paths where a network failure is a hard stop still report.
  • handleApiError now has a certificate branch that surfaces Node's own --use-system-ca hint plus the NODE_EXTRA_CA_CERTS route, so people on intercepting proxies learn how to fix their environment.

Nothing about the fallback behaviour changes — the suppressed errors were already handled.

Note this is one of two surfaces; the larger share of this noise comes from the MCP server's StateManager catch-all in PostHog/posthog, which needs the same treatment and ships separately.

Test plan

pnpm build && pnpm test && pnpm lint — 1697 tests pass, lint clean (492 pre-existing warnings, zero new).

Verified against a real TLS rejection rather than hand-built errors, since the open question was whether axios preserves the code and message through its wrapper. Ran the wizard's own fetchSlackConnected against a local HTTPS server with a genuine self-signed cert:

  • Node produced code: DEPTH_ZERO_SELF_SIGNED_CERT with a message byte-identical to the one in the reported error-tracking issue, and name: Error (axios propagates the raw TLS error rather than wrapping it — which is why the new branch sits above the axios.isAxiosError checks).
  • handleApiError returned the actionable hint instead of "Failed to fetch user data".

Then drove the real SlackConnectScreen component against that server:

  • With the fix: poll runs, TLS handshake rejected, zero captures.
  • With the guard reverted: one capture, reproducing the reported event exactly — same message, same step: 'slack_connected_check'.

New regression tests:

  • network-errors.test.ts — 30 cases, including the verbatim production error string and the negative cases (401 responses, ordinary TypeErrors, fs errnos, cyclic cause chains).
  • slack-connect-error-reporting.test.tsx — renders the real screen and locks both halves: an unreachable network stays out of error tracking, a 401 still reports. Mutation-checked — disabling the guard fails the first test and leaves the second passing, so the harness is genuinely sensitive.
  • api-error-handling.test.ts — the certificate hint, plus 401/403/404 messages unchanged.

Created with PostHog Desktop from this inbox report.

…ions

A machine behind a TLS-intercepting corporate proxy, or one missing an
intermediate CA, fails every HTTPS request before it reaches PostHog. The
Slack connectivity poll already degrades to its nudge copy when that
happens, but it still reported the throw to error tracking — so a user's
certificate problem arrived as a wizard exception we cannot act on.

Adds `isBenignNetworkError` / `isTlsTrustError` in `src/utils/network-errors.ts`,
following the `BENIGN_FS_ERROR_CODES` precedent in `bounded-fs.ts`: recognise
the expected-and-handled failures, log them to the debug file, and keep error
tracking for things we can act on. Transport-only by construction — an HTTP
response the server actually sent (401, 403, 5xx) is never matched, so real
API problems keep flowing through.

Applied to the two call sites that already degrade gracefully: the Slack
connectivity poll and flag evaluation.

`handleApiError` also grows a certificate branch. A certificate rejection is
an AxiosError with no `response`, so it used to fall through to a bare
"Failed to <operation>" and strip the one detail the user can act on; it now
names `--use-system-ca` and `NODE_EXTRA_CA_CERTS`.

Generated-By: PostHog Code
Task-Id: d186e757-b6f5-4e55-bc2b-e6f62eec1d4b
@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci basic-integration
  • /wizard-ci mcp-analytics
  • /wizard-ci revenue
  • /wizard-ci self-driving

Test an individual app:

  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
Show more apps
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci mcp-analytics/custom-dispatcher
  • /wizard-ci mcp-analytics/typescript-sdk
  • /wizard-ci revenue/stripe
  • /wizard-ci self-driving/astro
  • /wizard-ci self-driving/fastapi
  • /wizard-ci self-driving/nuxt
  • /wizard-ci self-driving/react-router
  • /wizard-ci self-driving/sveltekit

Results will be posted here when complete.

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.

0 participants