Skip to content

Multi-server: layout state not namespaced per server causes session loop after switching #18302

@Skeptomenos

Description

@Skeptomenos

Bug Description

When connecting OpenCode Desktop to multiple servers (local sidecar + remote opencode serve), switching between them leaves stale state from the previous server in persisted layout storage. This causes a "Session not found" polling loop when the Desktop tries to restore sessions that only exist on the other server.

Reproduction Steps

  1. Run opencode serve on a remote machine (e.g. http://remote:8472)
  2. In OpenCode Desktop, add the remote server via the server picker
  3. Switch to the remote server — open some projects, navigate to sessions
  4. Switch back to local sidecar
  5. Observe: ERROR service=server error=Session not found failed repeats every ~4 seconds in the sidecar log
  6. Error notifications accumulate with red badges on projects

Root Cause

Three pieces of persisted state in global.dat are keyed by directory path only, not by server origin + directory path:

1. lastProjectSession (packages/app/src/pages/layout.tsx:96-108)

// layout.page.v1 — global, not per-server
lastProjectSession: {} as { [directory: string]: { directory: string; id: string; at: number } }

rememberSessionRoute() (line 1189) stores session IDs keyed by directory. When switching servers, the previous server's session IDs remain. On return to local, navigateToProject() (line 1215) tries to restore these sessions via openSession() (line 1236) — fails because the session ID belongs to the remote server's database.

2. sidebar.workspaces (packages/app/src/pages/layout.tsx:227-261)

// layout.v6 — global, not per-server
sidebar: {
  workspaces: {} as Record<string, boolean>,  // keyed by directory path
}

Remote server workspace paths (e.g. /Users/remote-user/repos/project) persist in the sidebar layout after switching back to local. The Desktop then tries to bootstrap child stores for these non-existent local paths.

3. notifications (packages/app/src/context/notification.tsx:126-131)

// notification.v1 — global, single flat list
list: [] as Notification[]

Error notifications from the session restoration failures accumulate in a single list capped at 500, pushing out legitimate turn-complete notifications.

What IS correctly isolated

The project list itself is correctly namespaced — server.tsx:24-29 uses projectsKey(origin) to key store.projects[origin]. This is why the sidebar shows different projects per server. The runtime state is also isolated via <ServerKey keyed> which remounts the entire component tree on switch.

Proposed Fix

Namespace the three affected storage keys by server origin, matching the pattern already used in server.tsx:

Option A (minimal): Clear lastProjectSession entries and sidebar.workspaces entries for the previous server's paths when switching servers.

Option B (proper): Key these stores by origin() the same way store.projects already is:

// Instead of:
lastProjectSession: {} as { [directory: string]: SessionRoute }

// Use:
lastProjectSession: {} as { [origin: string]: { [directory: string]: SessionRoute } }

Environment

  • Version: 1.2.27
  • Two servers: local sidecar + remote opencode serve on a separate machine
  • The session loop fires ~every 4 seconds and generates error notifications with audible chimes

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)webRelates to opencode on web / desktop

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions