The active workspace's opencode instance was disposed/re-created ~1x/sec
when an org had cloud LLM providers configured. Root cause chain:
- POST /workspace/:id/desktop-cloud-sync clobbered the provider import
baseline (cloudImports.providers) in the workspace config with the
plugin DB copy (always empty), so every cloud provider sync pass
re-imported all org providers
- each re-import PATCHed an identical provider block, and the route
emitted a config reload event unconditionally, forcing an engine
reload per pass
- every reloadPending/reloadBusy flip changed the reloadCoordinator
context identity, recreating the session provider-auth store, whose
fresh sync-context key immediately started the next sync pass
Fixes:
- desktop-cloud-sync only updates desktopCloudSync state; the provider
baseline in the workspace config is preserved (and providers now diff
against the real baseline)
- PATCH /workspace/:id/config emits a reload event only when the runtime
opencode config actually changed; no-op writes skip listeners
- the session provider-auth store depends on the stable
markReloadRequired callback instead of the coordinator context value
- isCloudProviderOutOfSync normalizes model ids on both sides
- cloud provider sync aborts when the import baseline cannot be read
instead of treating it as empty and re-importing everything
Summary
Fixes the customer-reported engine lifecycle loop where the active workspace's OpenCode instance is disposed and re-created continuously (~1x/sec), with the status bar stuck flashing "Reloading OpenCode config" and the
/eventSSE cyclingconnected → disconnectedat ~1 Hz. Matches the field report from Go Autonomous (0.17.8-alpha + 0.17.13) and the customer's hunch that it only happens when org inference providers are configured.Root cause (verified live on a Daytona repro)
The loop is a cloud-provider sync reconcile feedback cycle:
POST /workspace/:id/desktop-cloud-syncerased the provider import baseline on every call (primary): it injectedcloudImportsfrom the cloud-plugin DB — whoseprovidersrecord is always empty — and wrote that whole object back into the workspace config, wipingcloudImports.providersthat the provider import had just persisted. The app calls this endpoint right after every provider import, so the baseline was erased immediately after being written. Every sync pass then saw "never imported" and re-imported all org providers.PATCH /workspace/:id/configemitted aconfigreload event unconditionally — even for a byte-identical no-op provider patch — so every re-import forced an engine reload (dispose→create of the active workspace instance).useMemodepended on thereloadCoordinatorcontext value, whose identity changes on eachreloadPending/reloadBusyflip. Every fresh store immediately started another cloud sync pass (empty sync-context key) → goto 1. This is why the loop followed the active workspace and background workspaces stayed stable.isCloudProviderOutOfSynccompared trimmed baseline model ids against raw live ids (whitespace/empty ids ⇒ permanently out-of-sync), and a failed baseline read was treated as "no imports" (re-import storm) instead of aborting the pass.Diagnostic fingerprint on the repro box:
runtime_opencode_configshad the provider, whileopenwork_workspace_configs.cloudImports.providersstayed{}forever, and den-api logs showed continuousGET /v1/llm-providers/:id/connectreconciles.Fixes
desktopCloudSyncstate (via the merge updater); the provider import baseline in the workspace config is preserved, and providers now diff against the real baseline.writeRuntimeOpencodeConfigdetects no-op writes (skips the upsert and the write listeners) and the route only emits a reload event when the runtime config actually changed.markReloadRequiredcallback instead of the whole coordinator context value — the store is no longer recreated on reload flips (settings route already did this).Reproduction & proof (Daytona, seeded cloud, signed in as Alex/Acme)
Setup: seeded OpenWork Cloud (Acme Robotics demo org,
alex@acme.test) on a Daytona server sandbox, one custom org LLM provider ("Acme Azure Foundry", 2 models — mirroring the customer's Azure Foundry custom provider), Electron desktop in a second Daytona sandbox connected to that Den.dev(pre-fix)creating instancefor the active workspaceGET /llm-providers/:id/connectreconcilescloudImports.providers{}forever (erased each sync)The pre-fix loop only stopped when the sandbox's Den connectivity was cut (sync pass fails before reconciling) — confirming the mechanism.
fraimz (frame-by-frame proof with the full user journey — handoff sign-in, org pick, provider auto-import witnessed in the workspace config, 60 s quiet window) is posted as a comment below.
Tests
pnpm --filter openwork-server test -- src/runtime-opencode-config-store.test.ts src/runtime-config-patch-reload.e2e.test.ts src/desktop-cloud-sync-preserves-imports.e2e.test.ts src/desktop-cloud-sync.test.ts— 12 pass, 0 fail (includes new: no-op write skips listeners; identical provider PATCH emits no second reload event; desktop-cloud-sync preservescloudImports.providers)pnpm --filter @openwork/app exec bun test src/react-app/domains/connections/provider-auth/cloud-provider-config.test.ts— 4 pass, 0 fail (new: symmetric out-of-sync comparison incl. whitespace model ids)pnpm --filter openwork-server typecheck/pnpm --filter @openwork/app typecheck— cleanpnpm fraimz --flow provider-sync-stable-engine --cdp-url <daytona-electron>— PASSED (5/5 frames)Follow-up
The settings-open engine reloads (cloud-MCP re-mint per settings mount, provider-sync target race, workspace re-select) are fixed in #2544.