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) Closes anomalyco#32379
|
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
|
Thanks for the careful review — all five points were real gaps in the PR as submitted. Addressed in ce12683:
Verified locally on Windows: |
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
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