Skip to content

fix(provisioning): retry connect failures and never show an empty error - #1014

Closed
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/provisioning-network-error-retry
Closed

fix(provisioning): retry connect failures and never show an empty error#1014
posthog[bot] wants to merge 1 commit into
mainfrom
posthog-code/provisioning-network-error-retry

Conversation

@posthog

@posthog posthog Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Problem

Someone signing up for PostHog through the wizard could hit a hard crash with a
completely blank error message, ending the run for a user who does not have
an account yet — so the "email already associated → fall back to login" branch
never fires either.

Two causes, same spot:

  1. The three axios.post calls in provisionNewAccount had a 30s timeout but
    no retry and no network-error handling, even though the repo already ships
    a retry-with-backoff helper in src/lib/fetch-retry.ts.
  2. When Node's happy-eyeballs connect fails, it collapses the per-address
    failures into an AggregateError whose own message is the empty string,
    and axios copies that empty message onto the AxiosError it rethrows
    (AxiosError.from). Every caller read error.message, so the signup screen
    logged Failed to create account: with nothing after the colon, and the
    exception reached error tracking with empty $exception_values.

Changes

  • src/lib/retry.ts — extract the bounded retry-with-backoff loop out of
    fetch-retry.ts so the skills-server fetches and the provisioning POSTs share
    one implementation instead of two. fetchWithRetry keeps its existing
    behaviour and aggregated failure message.
  • src/utils/network-errors.ts — walk an error's errors/cause chain for
    a usable code and message, decide whether a transport failure is worth
    retrying, and build a NetworkError whose message names the host and the
    remediation (Couldn't reach us.posthog.com — check your network, VPN, or proxy and try again. (getaddrinfo ENOTFOUND us.posthog.com after 3 attempts)).
  • provisionNewAccount retries transient transport failures only. An error
    carrying a response is never re-POSTed — the server already processed it —
    and the one retried call that isn't naturally idempotent, account_requests,
    reuses its caller-generated id as the idempotency key.
  • The three call sites (signup screen, wizard provision, CI install) unwrap
    rather than reading error.message, so none of them can print a bare colon
    again; the signup screen also reports a non-empty message to error tracking.

Test plan

Reproduced the real failure rather than only mocking it: a hostname resolving to
two addresses that both refuse forces the genuine happy-eyeballs
AggregateError (confirmed message: "", code: ECONNREFUSED,
errors[0].message: "connect ECONNREFUSED …").

Driving the actual signup path (getOrAskForProjectData({ signup: true }))
against it:

before  ✖  Failed to create account:
after   ✖  Couldn't reach <host> — check your network, VPN, or proxy and try
           again. (connect ECONNREFUSED 127.0.0.1:1 after 3 attempts)

Same before/after through wizard provision, including JSON mode
({"error":"","code":"provisioning_failed"} → a populated message with
"code":"network_error"). Retries observed taking ~1.5s across 3 attempts.

New unit tests cover the retry loop, the unwrapping/formatting, and
provisioning's behaviour on transport failure (retries and recovers, retries all
three POSTs, exhausts to a NetworkError, and does not retry an HTTP error
response or an already-existing account). pnpm build && pnpm test && pnpm lint
all pass — 1659 tests, 0 lint errors.


Created with PostHog Desktop from this inbox report.

The three provisioning POSTs had no retry and no network-error handling, so a
single failed connect ended the signup run. Worse, when Node's happy-eyeballs
connect fails it collapses the per-address failures into an AggregateError whose
own `message` is the empty string, and axios copies that empty message onto the
AxiosError it rethrows — so the signup screen printed "Failed to create
account:" with nothing after the colon, and error tracking got `[""]`.

- `src/lib/retry.ts`: extract the bounded retry-with-backoff loop out of
  `fetch-retry.ts` so both the skills fetches and the provisioning POSTs share
  one implementation. `fetchWithRetry` keeps its aggregated failure message.
- `src/utils/network-errors.ts`: unwrap an error's `errors`/`cause` chain for a
  usable code and message, decide whether a transport failure is retryable, and
  build a `NetworkError` that names the host and the remediation.
- `provisionNewAccount` retries transient transport failures only. An error
  carrying a response is never re-POSTed (the server already processed it), and
  the one retried call that isn't naturally idempotent, `account_requests`,
  reuses its caller-generated id as the idempotency key.
- The three call sites (signup screen, `wizard provision`, CI install) unwrap
  instead of reading `error.message`, so none of them can print a bare colon.

Generated-By: PostHog Code
Task-Id: 6645ad7a-b75d-417a-a265-bb9f177968ed
@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.

@rafaeelaudibert
rafaeelaudibert marked this pull request as ready for review July 28, 2026 21:48
@rafaeelaudibert
rafaeelaudibert requested a review from a team as a code owner July 28, 2026 21:48
@gewenyu99

Copy link
Copy Markdown
Collaborator

I fixed this in another PR

@gewenyu99 gewenyu99 closed this Jul 30, 2026
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.

1 participant