-
Notifications
You must be signed in to change notification settings - Fork 45
fix(analytics): stop filing user-side network drops as wizard errors #1182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c26c496
4b5b832
0175274
4fa7bb8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,49 @@ export function groupsFromUser( | |
| return groups; | ||
| } | ||
|
|
||
| /** | ||
| * Transport-level errno codes that mean the user's own network or machine | ||
| * dropped a connection mid-call, not that the wizard is broken. Every caller | ||
| * that hits these already degrades on its own — the Slack poll falls back to | ||
| * the connect nudge, a project-tree walk skips the entry, an API caller | ||
| * retries — so a capture adds only noise. And because each errno (and the | ||
| * host string Node folds into a raw socket message) fingerprints as its own | ||
| * error tracking issue, every one-off opens a fresh issue that buries real | ||
| * wizard bugs. Mirrors BENIGN_FS_ERROR_CODES in bounded-fs.ts. | ||
| */ | ||
| const BENIGN_TRANSPORT_ERROR_CODES: ReadonlySet<string> = new Set([ | ||
| 'ECONNRESET', // connection reset by peer / socket dropped | ||
| 'ECONNREFUSED', // nothing listening at the far end | ||
| 'ETIMEDOUT', // connection or network-backed filesystem read timed out | ||
| 'EHOSTUNREACH', // no route to host | ||
| 'ENETUNREACH', // no route to network | ||
| 'ENETDOWN', // local network interface down | ||
| 'EPIPE', // wrote to a closed socket | ||
| 'EAI_AGAIN', // temporary DNS resolution failure | ||
| 'ENOTFOUND', // DNS lookup failed — host not found (offline / captive portal) | ||
| ]); | ||
|
|
||
| /** | ||
| * The benign transport errno for an error, or undefined. Reads the `code` | ||
| * field first (raw socket and filesystem errors carry it), then falls back to | ||
| * the message — api.ts folds the errno into the ApiError message and drops | ||
| * `code`, so the parenthesized "(ECONNRESET)" wrapper is the only trace left. | ||
| * The fallback matches only that wrapper, never a bare mention: several callers | ||
| * wrap raw CLI stderr in a `new Error(...)` when an install fails, and that | ||
| * output can quote a benign errno (a "retrying ECONNRESET" log line) while the | ||
| * command actually failed for an unrelated reason. A bare substring match would | ||
| * silently drop those install failures — a class the team wants to see. | ||
| */ | ||
| function benignTransportCode(error: unknown): string | undefined { | ||
| const code = (error as NodeJS.ErrnoException | null)?.code; | ||
| if (code && BENIGN_TRANSPORT_ERROR_CODES.has(code)) return code; | ||
| const message = error instanceof Error ? error.message : ''; | ||
| for (const candidate of BENIGN_TRANSPORT_ERROR_CODES) { | ||
| if (message.includes(`(${candidate})`)) return candidate; | ||
| } | ||
|
posthog[bot] marked this conversation as resolved.
|
||
| return undefined; | ||
| } | ||
|
|
||
| const WIZARD_FLAGS: ReadonlySet<string> = new Set(WIZARD_FLAG_KEYS); | ||
|
|
||
| // Widen back to the SDK's shape — a filter on `true` never matches `'true'`. | ||
|
|
@@ -240,6 +283,23 @@ export class Analytics { | |
| } | ||
|
|
||
| captureException(error: Error, properties: Record<string, unknown> = {}) { | ||
| // 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) { | ||
| // This debug line is the only record of a dropped failure, and the same | ||
| // errno can come from unrelated operations (a Slack poll, a project-tree | ||
| // read, a doctor fetch). Keep the operation context (step/source) and the | ||
| // message so support can name what failed — callers already redact | ||
| // secrets from these before reporting. Mirrors bounded-fs's skip log. | ||
| const op = properties.step ?? properties.source; | ||
| logToFile( | ||
| `[analytics] skipped benign transport error (${benign})${ | ||
| op ? ` [${String(op)}]` : '' | ||
| }: ${error.message}`, | ||
| ); | ||
| return; | ||
|
posthog[bot] marked this conversation as resolved.
|
||
| } | ||
|
Comment on lines
+286
to
+302
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Global filter hides fatal service failuresWhy we think it's a valid issue
Issue description
Suggested fixMake suppression opt-in for callers that recover. For example, add an option and call Prompt to fix with AI (copy-paste)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
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 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. |
||
| this.client.captureException(error, this.distinctId ?? this.anonymousId, { | ||
| team: ANALYTICS_TEAM_TAG, | ||
| ...this.tags, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.