Skip to content

Desktop app: session list stuck on "Loading..." — window gets 401 from its own background service #53834

Description

@carotin000

Description

The desktop window never loads sessions. The project list and session list stay
empty and show the "Loading..." placeholder forever. The sessions are NOT lost —
the background service serves them correctly; only the window fails to read them.

The cause looks like authentication between the window and its own background
service. The window keeps 4 established connections to the service on
127.0.0.1:49374, but every request returns 401 Unauthorized — repeated about
once per second in the server log for /api/info and /api/event.

What I checked to isolate it to the window handshake:

  • GET /global/health against the service returns 200.
  • GET /api/session with a valid Basic password returns 200 with all sessions.
  • Auth is HTTP Basic: Authorization: Basic base64("opencode:<password>").
    Tested and rejected: Bearer token, x-opencode-password header, Basic without
    the opencode username.
  • The window's Local Storage has no server-connection entry at all, so the
    password cannot be supplied or persisted from the renderer side — it is
    expected to arrive from the main process and does not.
  • The same frontend on the same backend loads everything correctly in a plain
    browser once the address and password are entered on the "Connect to server"
    screen.

Similar to #50369, but in the desktop window instead of a manually started
opencode serve.

Steps to reproduce

  1. Launch the desktop app.
  2. Project list and session list stay empty, showing the "Loading..." skeleton.
  3. In %USERPROFILE%\.local\share\opencode\log\opencode.log, observe repeated
    http.status=401 for /api/info and /api/event, about once per second.

Tried, none of it helped:

  • Full app restart (service starts fresh, reason: missing, still 401)
  • Clearing Local Storage, WebStorage, Session Storage and all caches
  • Killing all opencode-cli.exe processes before launch
  • Downgrading to 2.0.23 — identical behaviour
  • A manually started server instance works fine in the browser

Version

2.0.24 (desktop and background service). Also reproduced on 2.0.23.
Started on 2026-10-07 around 21:00; the same build worked before that.

OS

Windows 10 x64, Intel Core i5-10300H

Plugins

none

Terminal

not applicable (desktop app)

Image

Activity

  1. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and for narrowing it down to the window rather than the service. This has to be reproduced in the desktop app on Windows, which our automated environment can't run, so a maintainer with that setup will need to look at it.

    Some background that may help: the desktop window never stores the password itself. The app's main process adds the Authorization header to the window's requests, but only when the request's address exactly matches the service's stored address (for example 127.0.0.1:49374 and not localhost:49374). So an empty Local Storage is expected, and a mismatched address would produce exactly these 401s.

    If you can, these would help:

    • Lines near the top of opencode.log from a fresh launch that mention v2 CLI background service ready, especially the logged url / hostname.
    • Whether you set a default server or a custom server URL in the desktop app, or changed anything around 2026-10-07 (proxy, VPN, security software, hosts file).

    Related, for reference: #50369 (same 401 symptom with opencode serve), #51075, #47062.

  2. carotin000 commented on Oct 8, 2026

    @carotin000
    Author

    Fresh launch, from main.log:

    [2026-10-08 04:50:53.263] [info]  (main) v2 CLI background service ready {
      version: '2.0.23',
      probed: true,
      url: 'http://127.0.0.1:49374',
      hostname: '127.0.0.1',
      port: '49374'
    }
    

    No custom server URL was ever set — this is the service the app starts for itself.

    What changed on/around 2026-10-07: a VPN/TUN tunnel client is installed and running on this machine. It appears as a network adapter happ-xray ("Happ Tunnel"), status Up, and it holds the default route (NextHop 0.0.0.0, RouteMetric 0). Loopback still routes normally (127.0.0.0/8 -> Loopback Pseudo-Interface 1), and NO_PROXY is set to 127.0.0.1,localhost,::1.

    The timeline matches: the app worked normally until roughly 2026-10-07 21:00, and in that same session the desktop updater began failing with net::ERR_NAME_NOT_RESOLVED (22:42) and then Cloudflare 1101 errors — consistent with the tunnel being active at that point. The breakage starts when the tunnel comes on.

    Other checks so far:

    • Windows system proxy is disabled: ProxyEnable=0, no ProxyServer, no PAC/AutoConfigURL.
    • hosts has no entries for localhost/127.0.0.1; it only contains blocklists for Adobe, Telegram and Discord CDN hosts.
    • The window opens 4 TCP connections to 127.0.0.1:49374, so it reaches the service by IP, not by hostname. So the "exact address match" theory would mean the mismatch is on the path/port rather than the hostname.

    Next step on my side: disable the tunnel, relaunch the app, and check whether the 401s disappear. I will report back with the result.

  3. carotin000 commented on Oct 8, 2026

    @carotin000
    Author

    Update: I disabled the tunnel completely (adapter removed from the route table, default route back to the normal Wi-Fi gateway) and relaunched. Still 401, so the TUN tunnel is not the cause.

    Fresh launch log line, unchanged:

    [2026-10-08 05:16:12.162] [info]  (main) starting v2 background service
    [2026-10-08 05:16:12.175] [info]  (main) v2 CLI background service ready {
      probed: true,
      url: 'http://127.0.0.1:49374',
      hostname: '127.0.0.1',
      port: '49374'
    }
    

    I went through the shipped main-process bundle to find the code you described, and I think I have located the exact gate. In resources/app.asar there is a single onBeforeSendHeaders registration:

    function U9(e, t) {
      if (!(!e?.password || !URL.canParse(t) || new URL(t).origin !== e.url))
        return `Basic ${Buffer.from(`opencode:${e.password}`).toString(`base64`)}`
    }
    
    function q9(e) {
      e.webContents.session.webRequest.onBeforeSendHeaders(
        { urls: [`http://127.0.0.1/*`, `http://localhost/*`] },
        (e, t) => {
          let n = e.frame,
              r = !!n && n.parent === null && je(n.url) && U9(H9(), e.url);
          r && !N9(e.requestHeaders, `Authorization`) &&
            P9(e.requestHeaders, `Authorization`, r),
          t({ requestHeaders: e.requestHeaders })
        }
      )
    }

    So the header is only attached when all of these hold:

    1. H9()?.password is non-empty — the server setting carries a password.
    2. new URL(requestUrl).origin === H9().url — exact origin match (http://127.0.0.1:49374).
    3. details.frame.parent === null — top-level frame.
    4. je(frameUrl) passes.
    5. The URL matches the filter http://127.0.0.1/* or http://localhost/*.

    My window reaches the service by IPv4, so 5 holds:

    LocalAddress LocalPort RemotePort RemoteAddress
    127.0.0.1     16958      49374      127.0.0.1
    127.0.0.1     33247      49374      127.0.0.1
    127.0.0.1     16960      49374      127.0.0.1
    127.0.0.1     16959      49374      127.0.0.1
    

    http://127.0.0.1/global/health returns 200 from the service, so the service is healthy.

    Two things I would appreciate help with:

    • Is condition 1 (H9()?.password) satisfied in a normal launch? It is the one I cannot observe from outside, and probed: true suggests the main process does reach the service, but maybe only for the probe and not for the renderer session state.
    • Is there a user-side workaround for now? The window has no "connect to server" screen, and its Local Storage holds no ServerConnection entry, so I cannot type the password in manually. Starting my own opencode serve and loading it in a browser works, which is how I have been able to work.

    Version 2.0.23 on Windows 10 x64. Happy to test a build or provide anything else you need.

  4. carotin000 commented on Oct 8, 2026

    @carotin000
    Author

    Tested on 2.0.25 as well (both desktop and background service at 2.0.25).

    Same symptom: the window stays on "Loading...", no sessions, no projects. So this is not resolved in the latest release.

    Relevant lines from main.log on 2.0.25:

    [2026-10-08 13:54:27.294] [info]  (main) v2 CLI version bundled { version: '2.0.25' }
    [2026-10-08 13:54:27.627] [info]  (main)    updater state changed { from: 'idle', to: 'checking' }
    [2026-10-08 13:54:28.494] [info]  (main)    updater state changed { from: 'checking', to: 'up-to-date' }
    [2026-10-08 13:54:29.035] [info]  (main)    v2 CLI background service ready {
    

    Service URL and hostname are still http://127.0.0.1:49374 / 127.0.0.1.

  5. carotin000 commented on Oct 9, 2026

    @carotin000
    Author

    Found the cause and a workaround - the home screen problem is caused by the
    service being hidden from the project list, not by the 401s.

    Steps to fix it:

    Settings -> Server -> menu on "Local server" -> "Show in project list"
    

    Before that, the home screen stayed on "Loading..." forever with no projects
    and no sessions, while session tabs opened correctly and Settings -> Projects
    showed everything. After enabling the option, all projects and all 13 sessions
    appeared at once, and it survives an app restart.

    Verified on 2.0.26, Windows 10 x64.

    I am leaving this open, because:

    • the option appears to be off after the update, so other users will hit this too
    • the 401 behaviour is a separate question - the window still does not attach
      the Authorization header when the request origin differs, and I could not
      verify from outside whether that is expected or a second defect

    Related for reference: #50369, #51075.

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