Skip to content

fix(analytics): stop filing user-side network drops as wizard errors - #1182

Draft
posthog[bot] wants to merge 4 commits into
posthog-self-driving/fixapi-stop-collapsing-and-double-61f357from
posthog-self-driving/fixwizard-stop-filing-user-network-256863
Draft

fix(analytics): stop filing user-side network drops as wizard errors#1182
posthog[bot] wants to merge 4 commits into
posthog-self-driving/fixapi-stop-collapsing-and-double-61f357from
posthog-self-driving/fixwizard-stop-filing-user-network-256863

Conversation

@posthog

@posthog posthog Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

  • The team gets a fresh error tracking issue every time a wizard user's own network drops a socket, even though the wizard handles the failure fine.
  • The Slack connect poll in SlackConnectScreen.tsx already degrades correctly on a failed check — it marks the project not-connected, stops polling, and shows the connect nudge — then still calls analytics.captureException. captureException filters nothing, so a user-side socket failure becomes a wizard error.
  • Each errno and host string fingerprints as its own issue, so every variant opens a separate issue that buries real wizard bugs. The same class shows up on filesystem walks over network-backed mounts and on wrapped API errors.

Changes

  • Quiet transport errnos once at the capture site (captureException in src/utils/analytics.ts), not at each call site — every reporter (SlackConnectScreen, bounded-fs, api.ts callers) flows through this one chokepoint.
  • Mirror the shape of BENIGN_FS_ERROR_CODES in bounded-fs.ts: a named set of benign transport codes (ECONNRESET, ETIMEDOUT, EHOSTUNREACH, and similar).
  • Detect the code from the error code field first, then fall back to scanning the message — api.ts folds the errno into the ApiError text and drops code, so (ECONNRESET) in the message is the only trace left.
  • A dropped error is written to the debug log instead of error tracking; genuine wizard errors still capture as before.

Test plan

  • New unit tests in analytics.test.ts cover: a raw socket error with a code, a host-unreachable socket error, a wrapped API error carrying the errno only in its message, a filesystem timeout, and a genuine error that must still capture.
  • pnpm build && pnpm test — 2026 tests pass. pnpm lint — 0 errors.

LLM context


Created with PostHog Desktop from this inbox report.

The Slack connect poll (and other callers) already degrade correctly when
the user's own network drops a socket, then still call
`analytics.captureException`. Each errno and host string fingerprints as a
separate error tracking issue, so every one-off opens a fresh issue that
buries real wizard bugs.

Filter transport-level errno codes once at `captureException`, reusing the
shape of `BENIGN_FS_ERROR_CODES` in `bounded-fs.ts`. Reads the error `code`
first, then falls back to scanning the message so API errors that fold the
errno into their text (and drop `code`) are caught too.

Generated-By: PostHog Desktop
Task-Id: 217e822f-fa35-4794-bb03-c40575e49608
@posthog

posthog Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 3 should fix, 1 consider.

Published 4 findings (view the review).

Resolved comments: 1 fixed, 1 already settled

@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 ai-observability
  • /wizard-ci basic-integration
  • /wizard-ci mcp-analytics
  • /wizard-ci replay-vision
  • /wizard-ci revenue
  • /wizard-ci self-driving
  • /wizard-ci warehouse
  • /wizard-ci warehouse-seeded

Test an individual app:

  • /wizard-ci ai-observability/anthropic
  • /wizard-ci ai-observability/groq
  • /wizard-ci ai-observability/manual-capture
Show more apps
  • /wizard-ci ai-observability/openai
  • /wizard-ci ai-observability/openai-agents
  • /wizard-ci ai-observability/vercel-ai
  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/flutter
  • /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 replay-vision/javascript-node
  • /wizard-ci replay-vision/next-js
  • /wizard-ci replay-vision/react-vite
  • /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
  • /wizard-ci warehouse/monorepo-env
  • /wizard-ci warehouse/multi-source-next
  • /wizard-ci warehouse/stripe-node
  • /wizard-ci warehouse/zero-source
  • /wizard-ci warehouse-seeded/next-stripe
  • /wizard-ci warehouse-seeded/next-stripe-declined

Results will be posted here when complete.

@posthog

posthog Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

PostHog Review

Found 3 should fix, 1 consider.

Comment thread src/utils/analytics.ts
Comment on lines +280 to +286
// Drop transport-level failures on the user's side. They never mean the
// wizard is broken and each variant opens its own error tracking issue.
const benign = benignTransportCode(error);
if (benign) {
logToFile(`[analytics] skipped benign transport error (${benign})`);
return;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Global filter hides fatal service failures

should_fix bug

Why we think it's a valid issue
  • Checked: every non-test caller of captureException/captureUnknown, the error shape that handleApiError builds, and the fatal-exit path in src/lib/runners/run-wizard.ts.
  • Found: handleApiError at src/lib/api.ts:298-302 returns new ApiError(\Failed to ${operation} (${axiosError.code ?? 'network error'})`)for every transport failure. The errno therefore sits in the message of each API error, so the message scan atsrc/utils/analytics.ts:101-104` drops it.
  • Found: Three capture sites report and then rethrow, so the failure is fatal and not recovered: src/utils/setup-utils.ts:672 (step: 'wizard_login', then throw error), src/utils/setup-utils.ts:552 and 585 (CI project lookup, then rethrow), and src/lib/programs/posthog-doctor/fetch.ts:24 (throw apiError). This contradicts the new comment at src/utils/analytics.ts:73-76, which states that every caller of these codes already degrades on its own.
  • Found: No other signal replaces the dropped event on the login path. The fatal catch at src/lib/runners/run-wizard.ts:249-281 logs to file, prints to the console, and calls process.exit(1). It never captures, and it never calls analytics.shutdown, which is the only source of the setup wizard finished terminal event (src/utils/analytics.ts:399-421, called from wizard-abort.ts:80, linear.ts:301, orchestrator-runner.ts:1099, start-tui.ts:90). The single captureUnknown at setup-utils.ts:672 was the only report for a failed login.
  • Found: The codebase already uses call-site filtering for the equivalent case. src/utils/bounded-fs.ts:41-44 drops benign filesystem codes inside its own reporter, where the caller is known to continue. This is the precedent the suggestion asks for.
  • Impact: If the PostHog API host becomes unreachable, or a build points at a wrong host or port, every run dies at login with ECONNREFUSED, ECONNRESET, or ETIMEDOUT, and Error Tracking records nothing at all. The team keeps no exception event and no terminal event for that run. This is a swallowed error that hides a failure, and the trigger and the consequence are both concrete.
Issue description

captureException reports both recoverable and fatal failures. Login, CI, and health-check paths capture errors before they rethrow them. A reset, refusal, or timeout now returns here without an error event. An errno cannot show whether the user, PostHog, or the wizard caused the failure. A service outage can therefore stop the wizard while Error Tracking stays silent.

Suggested fix

Make suppression opt-in for callers that recover. For example, add an option and call captureException(error, properties, { suppressTransportErrors: true }) from the Slack poll and filesystem reporter. Keep the default capture path for fatal failures. Add tests for both recovered and rethrown transport errors.

Prompt to fix with AI (copy-paste)
## Context
@src/utils/analytics.ts#L280-286

<issue_description>
`captureException` reports both recoverable and fatal failures. Login, CI, and health-check paths capture errors before they rethrow them. A reset, refusal, or timeout now returns here without an error event. An errno cannot show whether the user, PostHog, or the wizard caused the failure. A service outage can therefore stop the wizard while Error Tracking stays silent.
</issue_description>

<issue_validation>
- **Checked:** every non-test caller of `captureException`/`captureUnknown`, the error shape that `handleApiError` builds, and the fatal-exit path in `src/lib/runners/run-wizard.ts`.
- **Found:** `handleApiError` at `src/lib/api.ts:298-302` returns `new ApiError(\`Failed to ${operation} (${axiosError.code ?? 'network error'})\`)` for every transport failure. The errno therefore sits in the message of each API error, so the message scan at `src/utils/analytics.ts:101-104` drops it.
- **Found:** Three capture sites report and then rethrow, so the failure is fatal and not recovered: `src/utils/setup-utils.ts:672` (`step: 'wizard_login'`, then `throw error`), `src/utils/setup-utils.ts:552` and `585` (CI project lookup, then rethrow), and `src/lib/programs/posthog-doctor/fetch.ts:24` (`throw apiError`). This contradicts the new comment at `src/utils/analytics.ts:73-76`, which states that every caller of these codes already degrades on its own.
- **Found:** No other signal replaces the dropped event on the login path. The fatal catch at `src/lib/runners/run-wizard.ts:249-281` logs to file, prints to the console, and calls `process.exit(1)`. It never captures, and it never calls `analytics.shutdown`, which is the only source of the `setup wizard finished` terminal event (`src/utils/analytics.ts:399-421`, called from `wizard-abort.ts:80`, `linear.ts:301`, `orchestrator-runner.ts:1099`, `start-tui.ts:90`). The single `captureUnknown` at `setup-utils.ts:672` was the only report for a failed login.
- **Found:** The codebase already uses call-site filtering for the equivalent case. `src/utils/bounded-fs.ts:41-44` drops benign filesystem codes inside its own reporter, where the caller is known to continue. This is the precedent the suggestion asks for.
- **Impact:** If the PostHog API host becomes unreachable, or a build points at a wrong host or port, every run dies at login with `ECONNREFUSED`, `ECONNRESET`, or `ETIMEDOUT`, and Error Tracking records nothing at all. The team keeps no exception event and no terminal event for that run. This is a swallowed error that hides a failure, and the trigger and the consequence are both concrete.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Make suppression opt-in for callers that recover. For example, add an option and call `captureException(error, properties, { suppressTransportErrors: true })` from the Slack poll and filesystem reporter. Keep the default capture path for fatal failures. Add tests for both recovered and rethrown transport errors.
</potential_solution>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed — this is a real gap at current head, not a stale flag. I traced every capture site: the global filter in captureException drops fatal transport failures, not just recovered ones.

  • The CI project lookup (fetchProjectDataWithApiKey/fetchProjectDataById in setup-utils.ts) and the doctor health-issues fetch (posthog-doctor/fetch.ts) both capture and then rethrow. handleApiError folds the errno into the message (Failed to … (ECONNRESET)) and drops code, so the message scan matches and the event is dropped before it's ever sent.
  • The rethrow lands on the fatal exit in run-wizard.ts, which logs to file and process.exit(1) without capturing and without calling analytics.shutdown — so neither an exception event nor a terminal event survives.

Net effect: if a build points at a wrong host/port, or the API times out, a run can die at login/CI/doctor with nothing in Error Tracking. The PR comment's premise that 'every caller already degrades on its own' doesn't hold for these rethrowing sites.

I'm not fixing this unattended because the fix is a design decision. The suggested opt-in suppressTransportErrors flag reverses this PR's central choice — 'quiet once at the capture site, not at each call site' — and adds a new option to the analytics API. There are a few defensible resolutions: (a) opt-in suppression at the two recovering callers (Slack poll + bounded-fs) while fatal paths keep capturing; (b) move filtering entirely back to those recovering sites and drop the chokepoint; or (c) accept the tradeoff, on the view that a true outage can't transmit a capture anyway and only the misconfigured-host case genuinely loses signal.

Human decision needed: keep the chokepoint filter and accept that fatal login/CI/doctor transport failures stop capturing, or make suppression opt-in so those fatal paths capture again. I'll implement whichever you choose.

Comment thread src/utils/analytics.ts
Comment thread src/utils/analytics.ts
Comment thread src/utils/analytics.ts
posthog Bot added 3 commits August 31, 2026 20:57
…ing transport noise

benignTransportCode's message fallback used message.includes(candidate),
a bare substring match. Several install-failure reporters (Codex/Claude MCP
add) wrap raw CLI stderr in a plain new Error(...) with no `code`, so the
message scan is the only classifier that runs on them. If that stderr merely
quoted a benign errno (e.g. a "retrying ECONNRESET" retry line) while the
command actually failed for auth/permission/config reasons, the whole install
failure was silently dropped from error tracking.

Narrow the fallback to the exact "(CODE)" wrapper api.ts emits
(`Failed to ... (ECONNRESET)`), which still matches the intended ApiError
cases — including the CI re-wrap that appends text after the wrapper — but no
longer fires on an errno embedded elsewhere in wrapped tool output. Add a
negative test proving an install failure whose stderr mentions ECONNRESET
still captures.

Generated-By: PostHog Desktop
Task-Id: 80a79146-2554-4271-88bc-894c3053ef14
… log line

The skip log for a benign transport error wrote only the errno. Since these
failures are deliberately withheld from error tracking, that debug line is the
only surviving record — and it's a user-facing support artifact (printed as
"Full logs: <path>" on failure and rendered in the RunScreen LogViewer). A bare
"(ETIMEDOUT)" can't be attributed to the operation that produced it, whether a
Slack poll, a project-tree read, or a doctor fetch.

Include the operation context (properties.step / properties.source) and
error.message alongside the errno, mirroring bounded-fs's skip log that this PR
already cites as precedent. Callers redact secrets from these fields before
reporting, and only step/source are read from properties, so no sensitive
values reach the log.

Generated-By: PostHog Desktop
Task-Id: 80a79146-2554-4271-88bc-894c3053ef14
ENOTFOUND is Node/Axios's DNS-lookup-failed errno — the code a machine
that is offline or behind a captive portal returns most often. It sits in
the same user-side transport-failure class as EAI_AGAIN (temporary DNS,
already allowlisted) and ECONNREFUSED, but was missing from
BENIGN_TRANSPORT_ERROR_CODES, so a wizard user with no working DNS still
opened a "Failed to fetch user data (ENOTFOUND)" error-tracking issue per
operation — the exact noise this PR removes for ECONNRESET/ETIMEDOUT.

handleApiError folds the errno into the ApiError message and drops `code`,
so the "(ENOTFOUND)" wrapper is the only trace; the existing message scan
in benignTransportCode now catches it. Adds a regression test that routes
an ENOTFOUND AxiosError through handleApiError into captureException and
asserts it is not reported.

Generated-By: PostHog Desktop
Task-Id: e271ea17-9e5a-4d29-93f1-74d5be2bd974
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