Skip to content

Desktop GUI sidebar empty when connected to WSL2 opencode serve #29766

Description

@Aerytgophilis

Summary

When connecting the Windows Desktop GUI (Electron) to an opencode serve instance running inside WSL2, the session sidebar remains completely empty even after:

  • The connection handshake succeeds (green indicator)
  • The server API returns sessions correctly via direct curl / fetch()
  • Sessions exist in the server SQLite database

Two root causes were identified:

  1. Frontend timing issue -- session loading is triggered by SSE events that fire before the workspace/project list is populated
  2. Path concatenation in WSL -- Windows paths sent by the GUI are incorrectly resolved inside WSL, causing project ID mismatches

Environment

Component Version / Detail
Desktop GUI v1.15.11 (packaged, Electron 41.2.1)
OS (Desktop) Windows 11 x64
WSL2 Ubuntu 24.04 LTS, kernel 6.6.87.2-microsoft-standard-WSL2
opencode serve (WSL) v1.15.11, started with --hostname 0.0.0.0 --port 8193
Connection Desktop GUI -> http://127.0.0.1:8193 (WSL port-forwarded)
Repo location Inside WSL filesystem (~/opencode_workspace, a git repo)

Note: The same WSL2 + Desktop GUI setup was tested on a different Windows 11 machine (office PC) and the sidebar displayed sessions correctly. This suggests the bug may be environment/state-specific rather than universally reproducible.


Architecture (Dual-Server)

The Desktop GUI has a dual-server architecture that is central to this bug:

+-----------------------------------------------------------+
|  Windows Desktop GUI (Electron)                           |
|                                                           |
|  Connection 1: vlocal sidecar (built-in, auto-spawned)    |
|    -> http://127.0.0.1:{dynamic_port}                     |
|    -> Windows-native opencode serve process               |
|    -> Database: %LOCALAPPDATA%\opencode\opencode.db       |
|                                                           |
|  Connection 2: User-configured WSL server                 |
|    -> http://127.0.0.1:8193                               |
|    -> WSL2 Ubuntu opencode serve process                  |
|    -> Database: ~/.local/share/opencode/opencode.db       |
|    (contains all historical sessions)                     |
+-----------------------------------------------------------+

Even when defaultServerUrl is set to the WSL server (http://127.0.0.1:8193), the vlocal sidecar is still spawned automatically on startup. The GUI initial session loading appears to depend on SSE events that fire before the workspace/project list is fully populated.


Steps to Reproduce

  1. In WSL2, start opencode serve from a git repo directory:

    cd ~/opencode_workspace
    opencode serve --hostname 0.0.0.0 --port 8193
  2. Open the Windows Desktop GUI (v1.15.11).

  3. Add a server connection to http://127.0.0.1:8193.

  4. Set it as default:

    // In DevTools Console
    api.setDefaultServerUrl("http://127.0.0.1:8193")
  5. Observe: The connection indicator shows green ("connected"), but the session sidebar is empty.


Evidence

1. Server API works correctly (curl / fetch)

$ curl -s "http://127.0.0.1:8193/session?roots=true&limit=55" | jq length
14

Running the same query from the GUI DevTools Console also returns all sessions:

fetch("http://127.0.0.1:8193/session?roots=true&limit=55")
  .then(r => r.json())
  .then(data => console.log("Sessions:", data.length, data.map(s => s.title)))
// -> Sessions: 14 ["Session Title 1", "Session Title 2", ...]

2. No session list request observed in WSL server log

After the GUI connects, no session list API call was observed in the WSL server log (at INFO level):

INFO  ... service=server global event connected

No GET /session request was logged at any point after the GUI handshake. The server.connected SSE event fires, but the session loading logic does not appear to be triggered. Note: session list requests may not be logged at INFO level, so this observation is based on the available log output.

3. GUI is connected to both servers simultaneously

$ netstat -ano | findstr "8193"
TCP    127.0.0.1:xxxx        127.0.0.1:8193        ESTABLISHED     {gui_pid}

The GUI maintains active TCP connections to both the vlocal sidecar and the WSL server, but only loads sessions from vlocal.

4. global.dat confirms the WSL server is the default

{
  "server": {
    "list": [{"type": "http", "http": {"url": "http://127.0.0.1:8193"}}],
    "projects": {"local": [{"worktree": "C:/Users/{user}/opencode_workspace", "expanded": true}]},
    "lastProject": {"local": "C:/Users/{user}/opencode_workspace"}
  }
}

Despite the correct server and project configuration, sessions are not loaded.

5. Confirmed by asar reverse-engineering

Decompiling app.asar reveals the session loading flow:

GUI renderer
  -> serverSync.project.loadSessions(project.worktree)
  -> loadRootSessionsWithFallback({ directory, limit, list })
  -> serverSDK.client.session.list({ directory, roots: true, limit })
  -> HTTP GET /session?directory=XXX&roots=true&limit=55

The loadSessions call is triggered by the server.connected SSE event handler, but at that point M.children (the registered workspace/project list) is still empty, causing the iteration to produce no session-loading actions.


Root Cause 1: Frontend Timing (SSE vs. Project Hydration Race)

The session sidebar loading is driven by SSE event handlers in the renderer process. When server.connected fires:

  1. The handler iterates over M.children (registered workspaces/projects)
  2. For each workspace, it calls loadSessions(worktree)
  3. But when this event fires, M.children is empty
  4. No sessions are ever loaded

This is a race condition between SSE connection establishment and project/workspace state hydration. The server.connected event arrives before the project restore logic has finished populating the workspace list.

The vlocal sidecar works around this because it starts first (synchronously with the app) and its project list is loaded from local state before any remote connections are attempted.


Root Cause 2: Path Concatenation in WSL

Even if the timing issue were resolved, a second bug prevents session matching.

The chain:

Step 1: GUI reads worktree from global.dat
        -> "C:/Users/{user}/opencode_workspace"

Step 2: GUI sends this as the directory parameter
        -> GET /session?directory=C:/Users/{user}/opencode_workspace&roots=true

Step 3: WSL server receives the path and calls AppFileSystem.resolve()
        -> path.resolve("C:/Users/{user}/opencode_workspace")

Step 4: On Linux, path.resolve() treats this as a relative path
        -> CWD is prepended: "/home/{wsl-user}/C:/Users/{user}/opencode_workspace"

Step 5: This path does not exist on the WSL filesystem
        -> projectV2.resolve() cannot find .git
        -> Falls back to projectID = "global"

Step 6: Sessions in DB were created from the actual git repo
        -> project_id = "{git_worktree_hash}" != "global"
        -> listByProject() WHERE clause: eq(project_id, "global") matches nothing
        -> Returns 0 sessions

Key code (AppFileSystem.resolve()):

function resolve42(p2) {
  const resolved = resolve$1(windowsPath2(p2));  // path.resolve + windowsPath
  try {
    return normalizePath2(realpathSync(resolved));  // realpathSync resolves symlinks
  } catch (e22) {
    if (e22?.code === "ENOENT")
      return normalizePath2(resolved);  // file not found: returns resolved path
    throw e22;
  }
}

On Linux (WSL), windowsPath2() is a no-op. path.resolve("C:/Users/...") treats the Windows path as relative, prepending the server CWD. The resulting path never matches the actual git repository location.

This is a variant of the path separator issue documented in #21340, but specific to the WSL cross-filesystem scenario.


Related Issues / PRs

  • #17451 -- Sessions keyed only by directory, no server identity component (architectural root cause of dual-server state collision)
  • #18302 -- Multi-server layout state not namespaced per server (OPEN, proposes Option B: namespace by origin())
  • #27837 -- Web server mode sidebar empty (frontend timing bug, same Root Cause 1)
  • #29387 -- fix: load sidebar sessions after project restore (open PR, unmerged)
  • #21340 -- Windows path separator mismatch causes session filtering failure (related to Root Cause 2)
  • #25565 -- Duplicate of [Windows] Web UI not showing sessions - path separator mismatch (backslash vs forward slash) #21340, contains detailed API testing evidence
  • #19348 -- Sessions not shown in sidebar when connected to non-localhost server (same symptom, DB normalization only partially effective)
  • #17765 -- Windows Desktop loses session history after restart (project/worktree mapping mismatch)

Workaround

Use opencode CLI (TUI) inside WSL2 directly. The TUI runs entirely within the WSL environment so there is no path mismatch or dual-server timing issue.

Alternatively, work only through the vlocal sidecar (the built-in connection), accepting that historical sessions from the WSL database will not be visible in the sidebar.


Expected Behavior

When the Desktop GUI successfully connects to a remote opencode serve instance (including WSL2), the session sidebar should display all sessions from that server database.

Actual Behavior

The sidebar is empty. The vlocal sidecar empty database is used for session display, while the WSL server sessions are never loaded despite a successful connection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions