fix(app): load worktree inventory on demand and cap concurrent server requests - #47441
Merged
Conversation
Global bootstrap issued GET /api/worktree for every project row on the server, each booting a Location and running Git discovery. With a few hundred historical projects this saturated the browser's per-origin connection pool, stalling health probes and user actions. Project metadata is now loaded alone. A project's worktree inventory loads when a view shows it: a mounted Location (session or new-session view) or a project selected on Home. Loaded inventories survive metadata refetches and project.updated payloads, and worktree.updated refreshes only the affected project when it was loaded. Home resolves a session's project by directory first and by projectID second, so worktree sessions of not-yet-loaded projects keep their label and open at the project root.
Route every SDK request to a server through a FIFO queue capped at four in flight. Chromium allows six connections per origin; the event stream holds one and health probes use their own fetch, so the app's own bursts can no longer starve them inside the browser where nothing can observe it. /api/event is exempt. When the oldest queued request has waited two seconds, log the in-flight and queued requests once per ten seconds so debug exports show what the server was busy with.
Hona
force-pushed
the
worktree-fanout
branch
from
September 5, 2026 05:01
c0bd4ec to
16d60f0
Compare
Hona
enabled auto-merge (squash)
September 5, 2026 05:05
LocationGetOutput.project is required. Three spec-local mocks omitted it, so the Location provider's inventory lookup threw under those fixtures. Also answer GET /api/worktree with the root instead of an empty object.
This was referenced Sep 6, 2026
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 free
to 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.
Desktop periodically goes unresponsive with a red server dot while assistant output is still streaming. NetLogs from three captures show the same shape every time: hundreds of local API jobs created within seconds, then health preflights and the question-reply POST waiting on
SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUPfor 30–60 s. The server keeps answering200 OK; the browser simply has no socket to send on.Why the pool fills
flowchart LR subgraph before [Before] B1[bootstrap] -->|"GET /api/worktree × every project row (367 jobs / 263 dirs)"| S1[(server)] S1 -->|boots a Location per dir| E1[catalog / mcp / agent / ... events × 118 dirs] E1 -->|"eager re-fetch per event, per dir"| B1 end subgraph after [After] A1[bootstrap] -->|"GET /api/project (1)"| S2[(server)] A2[mounted Location or selected project] -->|"GET /api/worktree (1 per shown project)"| S2 endChromium allows 6 HTTP/1.1 connections per origin.
/api/eventholds one permanently and health probes need one, so the app has 4 to work with.loadProjectsQuerywas spending far more than that: it calledworktree.listfor every project in the database to fillProject.sandboxes(the server always returns[]). Each call boots a Location and runsgit worktree list, and it re-ran on everyconfig/agent/worktree.updatedand every reconnect. Those booted Locations then emit catalog events that the client echoes back as more reads — bounded now by what the user actually opens.This aligns Desktop with the TUI's usage pattern: sync only the Location you are showing.
Changes
Inventory loads when a project is shown —
packages/app/src/workspaces/inventory.tsuseWorkspaceLocation(session / new-session view) and from Home project selection, keyed by the metadata root and merged into the same global record.worktree.updatedrefreshes only a project that was loaded;project.updatedno longer wipes a loaded inventory (pre-existing bug: the payload carriessandboxes: []).projectID, so worktree sessions of not-yet-loaded projects keep their label and open at the project root.Request queue —
packages/app/src/runtime/server/request-queue.ts/api/eventis exempt. Stays saturated while there is demand, so a normal mount burst serialises instead of stalling inside Chromium where nothing can observe it.console.warnper 10 s lists every in-flight and queued request with its age. No toast; it lands in the debug export.Trade-offs
@opencode-ai/client(createDatare-fetching catalogs for any Location that emits, whether or not anything read them) is left as-is here so this PR stays app-only. With the fan-out gone it is bounded by booted Locations and throttled by the queue. The client-side fix is fix(client): refresh only loaded catalogs on location events #47443.strategyfrom the server to go away; that is the core follow-up, together with having/api/projectreturn the worktree table so the app never has to enumerate at all.LocationActivity60 min after their last session event; nothing re-boots them now.Related: #47428