Skip to content

feat(web): auto-retry transient "Cannot reach Maple API" errors#250

Merged
Makisuo merged 1 commit into
mainfrom
feat/api-error-auto-retry
Jul 22, 2026
Merged

feat(web): auto-retry transient "Cannot reach Maple API" errors#250
Makisuo merged 1 commit into
mainfrom
feat/api-error-auto-retry

Conversation

@Makisuo

@Makisuo Makisuo commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

What

Makes transient connectivity failures ("Cannot reach Maple API") recover automatically instead of sticking until a manual page reload.

Why

Users frequently saw the error persist even though reloading fixed it — the failures are transient. Two gaps caused this:

  1. HTTP layer failed fast on network errors. Both atom clients' HttpClient.retry predicates explicitly excluded transport errors (only 500/502/503 retried).
  2. No recovery once failed. effect-atom keeps the failed result; the error UI only offered a manual "Try again" button.

How

HTTP layer — new shared retry-policy.ts used by atom-client.ts and v2-atom-client.ts:

  • Transport errors retry up to 3 extra times with 300ms → 600ms → 1.2s exponential backoff.
  • Idempotent methods only (GET/HEAD/OPTIONS) — transport failures can occur after a request reached the server, so mutations never replay.
  • The 45s client timeout (TimeoutError) and aborted requests are excluded so a hang isn't multiplied.
  • The existing 5xx retries gain the same backoff (previously immediate re-fire).

UI layerformatBackendError now tags connectivity failures with kind: "network"; a new useNetworkAutoRetry hook (no-useEffect convention, useMountEffect + useEffectEvent) probes:

  • on a jittered 5s → 10s → 20s → 30s-cap backoff (jitter decorrelates multiple error panels on one screen),
  • immediately on the window online event or when the tab becomes visible again,
  • pausing while the tab is hidden or the browser reports offline.

ErrorState auto-probes whenever a retry callback exists and appends "Retrying automatically…" to the copy; RouteError gets the same treatment for route-loader failures. The manual "Try again" button stays as an instant probe.

Reviewer notes

  • Dashboard widget queries go over POST (/api/query-engine/execute), so they intentionally skip the HTTP-layer replay; they recover via the UI-layer probe re-running the query atom (verified working).
  • InvalidUrlError keeps the same error copy but is not tagged network — a bad URL never self-heals.
  • Backoff resets if a probe unmounts/remounts the error state (error → loading → error), effectively a steady ~5s poll during a long outage; the 5s floor keeps that cheap. Noted in the hook's doc comment.

Testing

  • Unit: kind: "network" assertions in error-messages.test.ts; method-scoping + timeout-exclusion in retry-policy.test.ts. Full apps/web suite: 97 files / 738 tests pass. bun typecheck clean.
  • End-to-end: loaded the dashboard, killed the API server mid-session — GETs retried with backoff, POSTs failed fast, error states showed "Retrying automatically…" and kept probing. Restarted the API: the entire dashboard (environments, stat cards, charts) repopulated with zero user interaction.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Network blips previously failed straight to the error UI and stuck there
until a manual reload: transport errors were explicitly excluded from the
HTTP clients' retry predicates, and nothing ever re-probed a failed atom.

HTTP layer: both atom clients now retry transient transport failures via a
shared retry policy — up to 3 extra attempts with 300ms/600ms/1.2s backoff,
idempotent methods only (mutations never replay), excluding the 45s client
timeout and aborted requests. The existing 5xx retries gain the same
backoff.

UI layer: formatBackendError tags connectivity failures with
kind: "network", and a new useNetworkAutoRetry hook probes on a jittered
5s→10s→20s→30s backoff plus immediately on the window `online` event /
tab-visible, pausing while hidden or offline. ErrorState shows "Retrying
automatically…" and RouteError self-recovers route-loader failures; the
manual "Try again" button stays as an instant probe.

Verified end-to-end: killed the API mid-session, watched GETs retry with
backoff and POSTs fail fast, then restarted the API and the dashboard
repopulated with zero user interaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pullfrog

pullfrog Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Your Pullfrog Router balance is exhausted.

You have a card on file but auto-reload is disabled, so runs paused once your balance went past the overdraft buffer.

Top up balance → · Enable auto-reload →

Pullfrog  | ⚠️ this action is pinned to a commit SHA, which freezes the cleanup step — switch to @v0 or keep the SHA fresh with Dependabot | Rerun failed job ➔View workflow run | via Pullfrog | Using Claude Opus𝕏

@Makisuo
Makisuo merged commit 13900b8 into main Jul 22, 2026
7 of 8 checks passed
@Makisuo
Makisuo deleted the feat/api-error-auto-retry branch July 22, 2026 23:12
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