Skip to content

desktop: browser pane launcher missing from side panel after update (gated on attached()) #60444

Closed as duplicate

Description

@mahatmamahardhikach

Summary

On OpenCode Desktop 2.2.2 (Windows), the Browser entry disappeared from the session side panel's + (new tab) launcher after updating from 2.2.1. Only Review, Terminal, and Open file remain, and there is no launcher left that can open a browser pane for the first time.

The launcher is registered only when attached(session) is already true, so once the pane is not attached there is no path back to it through the UI.

Environment

  • opencode version: Desktop app 2.2.2 (bundled CLI 2.2.2). A separate opencode on PATH reports 2.0.32.
  • OS: Microsoft Windows [Version 10.0.26300.9550] (win32 x64)
  • Terminal: OpenCode Desktop (Electron app, packaged: true); TERM=xterm-256color, TERM_PROGRAM unset
  • Shell: C:\WINDOWS\system32\cmd.exe
  • Install/channel: OpenCode Desktop, channel latest, auto-updater, packaged build at %LOCALAPPDATA%\Programs\ai.opencode.desktop
  • Active plugins: superpowers@git+https://github.com/obra/superpowers.git (2ce8aa1), rtk (local ~/.config/opencode/plugins/rtk.ts). MCP servers: playwright, dev-workflow, clinic-helper.

Reproduction

  1. On OpenCode Desktop 2.2.1, open a session and open the browser pane (or let it open from a http://localhost:... link in a message).
  2. Update the app to 2.2.2 and restart. The main log records the server restart on the version change:
    v2 CLI background service starting { reason: 'version-mismatch', previousVersion: '2.2.1' }
    
  3. Open a session and click + in the session side panel to open the new-tab / launcher list.
  4. Observe the launcher list.

Expected Behavior

Browser appears in the side panel + launcher so the browser pane can be opened, as it did on 2.2.1.

Actual Behavior

The launcher list only contains Review, Terminal, and Open file. There is no Browser entry, no browser tab, and no obvious way to open a browser pane from the UI.

Additional Context

Cause: the launcher is gated on attached()

packages/gui-plugins/src/browser/renderer.tsx registers the launcher only when the pane is already attached:

ctx.add(MenuItem, (): MenuItem | undefined => {
  const view = sessions.current()
  const value = model()

  // Only once the desktop's pane answers.
  if (!view || !value?.available(view) || !value.attached(view)) return undefined

  // A launcher of the side panel's new tab view, between Review and Terminal.
  return {
    menu: "session.panel",
    id: "open",
    ...
  }
})

And attached() requires an existing registration or existing browser state — packages/gui-plugins/src/browser/model.ts:358:

const attached = (session: Session) => {
  const value = attachment(session)

  return value?.registration !== undefined || !!value?.browser
}

So the launcher is a function of "a browser pane is already attached", but its run is open(session) — the action that opens the browser pane in the first place. When the pane is not attached (fresh app start, server restart on update, or a failed registration), the launcher can never render, and the user has no launcher to attach it again. The Open browser command and its keybind still work, which confirms the pane itself is fine — only the discoverable entry point is gone.

Contributing: the unsupported flag is sticky for the app's lifetime

model.ts:225 sets a permanent per-server flag on a single failed registration, and nothing ever clears it:

change: (next, mirror, native) => {
  if (next.error === "browser.pane.unsupported") {
    setState("unsupported", ref.server.id, true)

    return close(id)
  }

available() then returns false forever (model.ts:374), which also disables the Open browser command:

const available = (session: SessionRef) =>
  !state.unsupported[session.server.id] && !!session.id && session.server.compatible && !layout.narrow()

connection.ts:173 likewise latches blocked = true for browser.pane.unsupported and browser.pane.replaced, with no reset path. A server restart during an app update is exactly the situation where browser.pane.replaced can fire, after which the browser pane never comes back until the whole app is restarted.

Workaround

  • Command palette (Ctrl+K) → Open browser, or Ctrl+Alt+B. Once the pane opens, the launcher entry reappears for that session.
  • Or fully quit and relaunch OpenCode Desktop, which resets the sticky unsupported state.

Suggested fixes

  1. Register the side-panel launcher on available(session) only, and let run perform the first attach (drop the attached() requirement), or keep the launcher visible but enabled: false until attached.
  2. Clear state.unsupported[server.id] when the server reconnects, its version changes, or the pane's Ipc comes back, instead of latching it for the app's lifetime.

Notes

  • The browser pane itself is functional; this is a discoverability regression in the launcher, not a broken pane.
  • server.compatible is true and /api/info reports 2.2.2 on the desktop's own server (http://127.0.0.1:28894), so this is not a version-mismatch gate.
  • Unrelated but observed in the same session: the playwright MCP server exits with code 1 after a few minutes (mcp transport error ... "MCP server process exited with code 1"). Happy to file that separately if useful.

Activity

  1. opencodejr commented on Oct 30, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report and analysis!

    I think this is a duplicate of #60369. It reports the same thing: on OpenCode Desktop 2.2.2 for Windows, the Browser entry disappears from the side panel's + menu right after the update, leaving only Review, Terminal and Open file. The cause found there is that the browser part of the 2.2.2 desktop build fails to load. It shows up in main.log as scope finalizer failed { scope: 'browser', error: "SyntaxError: Identifier 'n' has already been declared" ... }. A fix (#60430) has been merged. It would help to check whether your %APPDATA%\ai.opencode.desktop\logs\...\main.log has that error.

    Your analysis of the attached() gating and the sticky unsupported / replaced state is already tracked in #60023, which proposes the same fixes. PR #60123 is open for it.

    Possible duplicates and related issues, ordered from most to least likely:

    The bot will close this issue as a duplicate in 1 day unless you reply explaining how your problem is different. For example, if your main.log has no SyntaxError and only the launcher behaviour from #60023 applies. The playwright MCP server exiting with code 1 is unrelated, so a separate issue for it would be welcome.

  2. mahatmamahardhikach commented on Oct 30, 2026

    @mahatmamahardhikach
    Author

    Correction: this is #60369. My earlier analysis in this comment was wrong, and I can now prove the chunk itself is broken.

    The decisive test

    I ran the same check that #60430 adds to the build (verifyBundles: parse every emitted out/main chunk with vm.SourceTextModule) directly against the installed resources/app.asar on this machine:

    650 chunk out/main diperiksa, gagal parse: 1
      BROKEN  out/main/pane-CwHDCUMq.js: Identifier 'n' has already been declared
    

    Exactly one chunk out of 650 fails to parse, and it is the browser pane chunk — the same Identifier 'n' has already been declared as #60369. No crash needed to see it.

    Why my main.log is clean (and why that misled me)

    pane-*.js is imported lazily, on the first attach:

    let r = () => t.pane ??= import(`./pane-CwHDCUMq.js`).then(...)

    On this machine no attach is ever attempted, so the chunk is never imported, so it never throws, so scope finalizer failed never appears. main.log for both 2.2.2 launches contains only updater lines; app starting count is 1 per log folder (no relaunch), and Crashpad is empty. That absence is not evidence of health — it is evidence the chunk was never reached.

    This matches what @RonnyWu noted in #60433: "the failure looks one-shot, on the first attach attempt after launch."

    What still holds, and what I retract

    Still true, but irrelevant to the bug — it only proves registration, not behaviour:

    command.catalog.v1 -> 70 entries, 7 browser commands
      browser.open -> {"plugin":"browser","title":"Open browser","keybind":"ctrl+alt+b"}
    ctrl+alt+b has exactly one owner (no collision)
    

    Retracted: my claim that "the browser pane itself is healthy" because layout.sessionTabs contains browser:tab_* entries. Those rows are stale — the newest write to name='plugin.browser' in drafts.sqlite is 2026-10-30T01:01:21Z, which is before the first 2.2.2 launch at 2026-10-30T01:06:51Z. The pane has written nothing since 2.2.2 was installed. Interpreting persisted tab layout as a live pane was a mistake.

    Also retracted: the open() / "no path to a first tab" theory. In the shipped bundle open() does send tabs.open; it fails earlier because there is no registration to send it over.

    Summary

  3. Hona commented on Oct 30, 2026

    @Hona
    Member

    Fixed in 2.2.3 by #60430.

    The desktop main-process bundle declared require twice, which broke the in-app browser and its browser.* tools.

    Please update to 2.2.3. In the desktop app, use Check for Updates, or install it from the release page. If the browser pane still doesn't appear afterwards, please reopen this issue with your main.log from the logs folder.

  4. opencodejr commented on Oct 31, 2026

    @opencodejr
    Contributor

    We haven't heard back, so I'm closing this as a duplicate of #60369. That issue covers the missing Browser entry on Desktop 2.2.2 for Windows after the update, and its fix (#60430) has been merged. Your analysis of how attached() hides the launcher and why the unsupported / replaced state persists until restart is tracked in #60023, with PR #60123 open for it. Please follow those issues for updates.

    If your main.log has no SyntaxError: Identifier 'n' has already been declared and the problem looks different, comment here and we can reopen. Thanks again for the detailed report!

  5. phoenixgao commented on Nov 10, 2026

    @phoenixgao

    Updated from 2.2.5 to 2.2.7, the browser panel is gone again @Hona

  6. ThomasCro33 commented on Nov 11, 2026

    @ThomasCro33

    It is gone, and it breaks my main workflow completely, please restore it as soon as possible.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions