Skip to content

feat(components): retry a workspace-runtime boot that failed - #21

Draft
pythonlearner1025 wants to merge 1 commit into
mainfrom
feat/retry-failed-workspace-runtime-boot
Draft

feat(components): retry a workspace-runtime boot that failed#21
pythonlearner1025 wants to merge 1 commit into
mainfrom
feat/retry-failed-workspace-runtime-boot

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

Draft, and offered for rejection. This is a behaviour change, not a bug fix. If the one-shot boot is deliberate, say so and I will close it.

The gap

createWorkspaceRuntime runs once (runtime-provider.tsx:255). When it throws, the catch does this (:328-334):

logRuntimeOperationError('runtime initialization', error);
if (disposed) return;
setRuntime(null);
setControlConnectionState('error');
setRuntimeInitializing(false);

…and then stops. The effect's dependency list (:355-369) holds the workspace slug, the workspace id, and localAgentRuntimeReady — a flag consulted only when platform.sync.mode === 'dual' (:211), i.e. Electron cloud desktop. On a browser-shaped surface against a machine there is no input left that can change, so nothing re-runs the effect. Anything gating on a live runtime stays stuck until the user reloads the page.

The window is not exotic: a machine's gateway answers well before its session daemon does, so a boot attempted in that gap fails and never runs again.

The change

  • bootGeneration — the one input a failed boot can move. The catch schedules a bump; the effect re-enters with the same inputs it had.
  • The attempt count lives in a ref, not state, so clearing it on success does not re-run the effect and tear down a runtime that just came up.
  • Backoff reuses computeLocalReconnectDelayMs (local-reconnect-loop.ts:16) rather than introducing constants — same 1s→30s curve with jitter the provider already uses for a machine slow to reconnect.
  • The timer is cleared in the existing cleanup, so a workspace switch or unmount cancels a pending retry.

25 insertions, one file.

Sequencing: this should land behind #19

A boot retry re-enters maybeClearLodyCacheOnBoot, and on main that call deletes the workspace's IndexedDB databases every time after a single Settings→Clear cache, because the memo resolves to the clear mode rather than to "already ran". So without #19 this change turns one stale flag into repeated local-cache wipes on a backoff loop.

This is confirmed from two directions, not hypothetical: #19 carries a test that fails on main with expected [ Array(2) ] to have a length of 1 but got 2, and the same interaction was measured independently downstream against a real IndexedDB. Please merge #19 first.

What ran, and what did not

Ran: tsgo --noEmit on @lody/components (clean), prettier --check (clean), oxlint (0 warnings, 0 errors).

No test. I did not add one, because exercising this means driving a createWorkspaceRuntime failure through the provider's effect and I did not want to introduce a mocking seam into a 400-line lifecycle on a change that may be rejected outright. If you want it, say so and I will write it.

Did not run pnpm check. pnpm install needs --ignore-scripts in this environment, and pnpm test:ci spans apps/cli, whose native better-sqlite3 binding is absent. CI is the authority.

`createWorkspaceRuntime` runs once. When it throws, the catch sets `runtimeAtom`
to null and the effect's dependency list holds only the workspace slug, the
workspace id and `localAgentRuntimeReady` — a flag that moves solely in Electron
`dual` sync mode. On a browser-shaped surface against a machine there is then no
input left that can change, so nothing re-runs the effect and anything gating on
a live runtime stays stuck until the user reloads the page.

The window is not exotic. A machine's gateway answers well before its session
daemon does, so a boot attempted in that gap fails and never runs again.

`bootGeneration` is the one input a failed boot can move: the catch schedules a
bump and the effect re-enters with the same inputs it had. The attempt count
lives in a ref rather than state, so clearing it on success does not re-run the
effect and tear down a runtime that just came up. Backoff reuses
`computeLocalReconnectDelayMs` rather than introducing constants, so a machine
slow to come up is waited out on the same curve the provider already uses for
one slow to reconnect.

This is a behaviour change, not a bug fix, and it is offered as a draft: if the
one-shot boot is deliberate, say so and this can be closed.

Model: claude-opus-5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant