From 29d3dc5ba2d54e1dd72267b7e34596f840269ca0 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Fri, 25 Sep 2026 14:01:53 -0700 Subject: [PATCH 1/2] fix(client): slow servers finish loading the thread list instead of loading it twice The shell and thread HTTP snapshot loads gave up after 6 s. On timeout the socket asked the same server for the same full snapshot, so a slow but alive server built it twice and the first build was thrown away. A long-uptime report showed shell loads at p50 7.6 s and max 11.9 s, and all 6 were abandoned. Older turn pages have no socket fallback, so they failed silently. Raise both deadlines to 20 s. The socket ping already detects a dead server. Rewrite the stale "cheaper socket fallback" comments to give the real reason for the fallback: setups where /api fails but /ws works. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../client-runtime/src/state/shellSnapshotHttp.ts | 10 +++++++--- .../client-runtime/src/state/threadSnapshotHttp.ts | 11 +++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/client-runtime/src/state/shellSnapshotHttp.ts b/packages/client-runtime/src/state/shellSnapshotHttp.ts index 84ab1a3f1d4b..fe5a3d5fcec1 100644 --- a/packages/client-runtime/src/state/shellSnapshotHttp.ts +++ b/packages/client-runtime/src/state/shellSnapshotHttp.ts @@ -12,9 +12,13 @@ import { environmentEndpointUrl } from "../environment/endpoint.ts"; import { ManagedRelayDpopSigner } from "../relay/managedRelay.ts"; import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts"; -// Bounded so a pathologically slow endpoint cannot block the (cheaper) socket -// fallback for long. The cached shell renders while this runs. -const DEFAULT_SHELL_SNAPSHOT_TIMEOUT_MS = 6_000; +// Long enough for a slow but alive server to finish. On timeout the socket asks +// the same server for the same full snapshot, so a short deadline only throws +// the first build away. The socket fallback is for setups where /api fails but +// /ws works, such as a proxy that blocks /api. A dead server is caught by the +// socket ping, which drops the session and interrupts this load. The cached +// shell renders while this runs. +const DEFAULT_SHELL_SNAPSHOT_TIMEOUT_MS = 20_000; /** * Load the environment shell snapshot (projects + thread shells) over HTTP diff --git a/packages/client-runtime/src/state/threadSnapshotHttp.ts b/packages/client-runtime/src/state/threadSnapshotHttp.ts index 9582ad30567b..da1156cc83e0 100644 --- a/packages/client-runtime/src/state/threadSnapshotHttp.ts +++ b/packages/client-runtime/src/state/threadSnapshotHttp.ts @@ -13,10 +13,13 @@ import { ManagedRelayDpopSigner } from "../relay/managedRelay.ts"; import type { RemoteEnvironmentRequestError } from "../rpc/http.ts"; import { executeAuthenticatedEnvironmentHttpRequest } from "./environmentHttpAuth.ts"; -// Bounded so a pathologically slow endpoint cannot block the (cheaper) socket -// fallback for long. The cached thread renders while this runs, so the wait only -// delays the transition to live data on the first open, not the initial paint. -const DEFAULT_THREAD_SNAPSHOT_TIMEOUT_MS = 6_000; +// Long enough for a slow but alive server to finish. On a cold open a timeout +// makes the socket ask the same server for the same snapshot again, and older +// turn pages have no fallback, so a short deadline only drops work. The socket +// fallback is for setups where /api fails but /ws works, such as a proxy that +// blocks /api. A dead server drops the socket session, which interrupts a +// cold-open load. Older turn pages wait for this deadline. +const DEFAULT_THREAD_SNAPSHOT_TIMEOUT_MS = 20_000; /** * Load a thread's detail snapshot over HTTP instead of embedding it in the From 139dd4da9c2b30a81309dd80540fdfa167e364b5 Mon Sep 17 00:00:00 2001 From: Theo Browne Date: Fri, 25 Sep 2026 18:07:22 -0700 Subject: [PATCH 2/2] docs(client): drop stale snapshot reference from the session state timeout comment The session state timeout comment said it was bounded like the snapshot fetches. The snapshot fetches now wait 20 s, so the comparison is no longer true. Co-Authored-By: Claude Opus 5.5 (1M context) --- packages/client-runtime/src/state/session.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client-runtime/src/state/session.ts b/packages/client-runtime/src/state/session.ts index 5787bd0e60b7..15848d95d965 100644 --- a/packages/client-runtime/src/state/session.ts +++ b/packages/client-runtime/src/state/session.ts @@ -30,8 +30,8 @@ function initialConfigOption( ); } -// Bounded like the snapshot fetches: a wedged environment must not pin the -// permissions check (and with it the settings UI) in a loading state for long. +// Bounded so a wedged environment cannot pin the permissions check (and with it +// the settings UI) in a loading state for long. const DEFAULT_SESSION_STATE_TIMEOUT_MS = 6_000; /**