Repository navigation
Description
What happened
While one provider is running "Update now" (e.g. a slow Muse update), turning a different provider (Claude, Codex, …) on or off in Settings → Providers does nothing until that update finishes. The switch doesn't move, then the toggle applies late once the update completes.
It isn't limited to the switch: every settings write to that environment waits behind the running provider update.
Steps to reproduce
- Settings → Providers, click "Update now" on a provider whose update takes a while.
- While it is still running, toggle a different provider off.
Expected: the toggle applies immediately; only the provider being updated is busy.
Actual: the toggle (and any other settings change, keybinding edit, add/remove instance) is held until the update RPC resolves.
Cause
serverEnvironment.updateProvider runs on the same client-side serial lane as all settings writes:
packages/client-runtime/src/state/server.ts:758-761:configConcurrency = { mode: "serial", key: environmentId }server.ts:1143-1148:updateSettingsusesconfigScheduler+configConcurrencyserver.ts:1390:mutateProviderInstance: updateSettings(what the provider enable switch calls viausePersistEnvironmentProviderInstanceMutation)server.ts:1370-1375:updateProvideralso usesconfigScheduler+configConcurrencypackages/client-runtime/src/state/runtime.ts:246-249: serial mode chains each command behind the previous promise for the key
The server's serverUpdateProvider handler (apps/server/src/ws.ts:2464 → ProviderMaintenanceRunner.updateProvider) only resolves after the install command, version verification and refreshAfterUpdate, so the update holds the environment's config lane for its whole duration. The switch's checked state comes from persisted settings (apps/web/src/components/settings/ProviderInstanceCard.tsx:1034-1038), so it appears stuck.
Nothing below the client blocks: the switch isn't disabled for other providers' updates, and on the server ProviderInstanceRegistry reconcile and ProviderRegistry.syncLiveSources don't contend with the update's lock.
Side effect: "Update all" (apps/web/src/components/ProviderUpdatesAction.tsx) is also serialized client-side, on top of the server's own per-lock-key queue.
Since the lane lives in packages/client-runtime, this affects web, desktop and mobile.
Suggested fix
Take updateProvider off the config lane. The server already serializes updates per install via providerMaintenanceCommandCoordinator (and reports the "Waiting for another provider update to finish" queued state), so the client queue adds nothing. Either:
- mirror
updateServer, which already has its ownupdateScheduler(server.ts:757, 831), or - use
concurrency: { mode: "singleFlight", key: ({ environmentId, input }) => JSON.stringify([environmentId, input.instanceId]) }.
A focused client-runtime test could assert that an updateSettings call resolves while an updateProvider call is still pending.
Related but distinct: #24998 (update state stuck at running server-side).
Activity