Uh oh!
There was an error while loading. Please reload this page.
fix(app): wait for local server readiness before bootstrap fan-out - #43370
Open
weiconghe wants to merge 2 commits into
Open
fix(app): wait for local server readiness before bootstrap fan-out#43370weiconghe wants to merge 2 commits into
weiconghe wants to merge 2 commits into
Conversation
- retry: treat network-layer TypeError as transient regardless of message locale, so a cold-starting local server recovers on the next attempt - app: add waitForServerReady() bounded health polling and gate the bootstrap query + per-directory fan-out on it (local servers only) Closesanomalyco#32379
Enough1122
commented
Aug 22, 2026
|
Address review feedback: - server-sync: propagate readiness failure instead of ignoring it — the bootstrap query and per-directory fan-out share ensureServerReady() and throw a localized 'Could not reach <url>' error when the server never becomes healthy; directory bootstrap failures surface through the same toast as other per-directory load failures - retry: make the TypeError rule an explicit retryOnTypeError opt-in so programming errors inside retried callbacks fail fast; every network call site in bootstrap.ts / server-session.ts opts in - server-health: bound each poll attempt by the remaining deadline (no wall-clock overshoot past timeoutMs) and forward the caller's abort signal into checkServerHealth, which now combines it with its per-attempt timeout signal instead of choosing one or the other - tests: cover never-ready gating, wall-clock bound, mid-flight abort, and the retry opt-in semantics
weiconghe
commented
Aug 24, 2026
ContributorAuthor
Thanks for the careful review — all five points were real gaps in the PR as submitted. Addressed in ce12683:
Verified locally on Windows: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
Closes#32379
Type of change
What does this PR do?
On desktop startup the render process fires a fan-out of
get/listrequests through the v2 client (throwOnError: true) as soon as the bootstrap query mounts. The local sidecar server is started in parallel, so the first requests can hit a socket that is not listening yet, and the bootstrap query dies with an unhandledTypeError: Failed to fetch(call stack: rendermain-*.jsfetch → request → Object.get).Two source defects contribute:
isTransientErrorinpackages/core/src/util/retry.tsclassifies transient errors purely by message text (TRANSIENT_MESSAGEScontains"failed to fetch"). That message is locale-dependent — on non-English systems the browser localizes it (e.g.ネットワークエラー), so the error is treated as non-transient and never retried. The cold-start race then surfaces as an unhandled exception instead of recovering on the next attempt.Changes:
packages/core/src/util/retry.ts— add an explicitretryOnTypeErroroption (default off).fetchrejects with a bareTypeErrorwhen the target is unreachable and its message is locale-dependent, so it cannot be matched by text; callbacks whose failures are exclusively network requests opt in, while any otherTypeError(a programming bug inside the callback) keeps failing fast after one attempt instead of being silently re-run.packages/app/src/context/global-sync/bootstrap.ts,packages/app/src/context/server-session.ts— every retried network call site opts in with{ retryOnTypeError: true }.packages/app/src/utils/server-health.ts— newwaitForServerReady(): bounded polling of the existing health check (defaults: 10s deadline / 250ms interval), reusingcheckServerHealth; resolvesfalseon timeout or abort instead of throwing. Each attempt is bounded by both its own budget and the time left before the overall deadline (min(pollMs*4, 1s, deadline - now)), so total wall clock stays withintimeoutMs; andcheckServerHealthnow combines the caller's abort signal with its per-attempt timeout signal instead of choosing one or the other, so an aborted caller cuts off the in-flight request.packages/app/src/context/server-sync.tsx— gate the globalbootstrapqueryFn and the per-directory fan-out (bootstrapInstance) onwaitForServerReady()via a sharedensureServerReady()helper, guarded byServerConnection.local(serverSDK.server)so remote server connections get no extra startup delay. When readiness fails, both gates now fail fast with a localizedCould not reach <url>error (reuses the existingapp.server.unreachablekey, rendered viaformatServerError) instead of silently firing the fan-out into a dead server; directory-bootstrap failures surface through the same toast used by sibling per-directory load failures.Relation to earlier attempts: #41650 / #32468 retried MCP queries only, #28792 / #28707 are older and closed. This PR fixes the retry classification itself and adds the startup gate, in different files — not a duplicate of those.
How did you verify your code works?
packages/core/src/util/retry.test.ts: 6 cases — TypeError retried when opted in; localized TypeError message retried when opted in; programming TypeError fails after one attempt by default; message-matched transient errors still retried without opting in; attempts exhausted rethrows; customretryIftakes precedence. Ran locally:bun test src/util/retry.test.ts(inpackages/core) → 6 pass, 0 fail.packages/app/src/utils/server-health.test.ts: 16 pass, 0 fail (bun test --conditions=solid --preload ./happydom.ts ./src/utils/server-health.test.ts). Includes the original 4waitForServerReadycases plus 3 new ones: never-ready gating pins the wiring contract used by both gates (readiness failure ⇒ no data requests fired into the dead server and the caller gets an error); a hung fetch cannot push total wall clock pasttimeoutMs(old behavior overshot to ~timeoutMs + pollMs*4 + pollMs); a mid-poll caller abort cuts off the in-flight health request. One pre-existingcheckServerHealthcase was updated from asserting signal identity to asserting abort propagation, because the request now correctly receives a derived signal combining the caller's signal with the per-attempt timeout.bun test --conditions=solid --preload ./happydom.ts→ 729 pass, 0 fail.Screenshots / recordings
N/A
Checklist