Built-in browser does not render or navigate on v0.0.46 nightlies; desktop browser tools report "No desktop browser is connected to this session"#17009

openedOct 8, 2026byGGabriel Silva

What happened

I use the nightly channel. Since the Orchestrator V2 nightlies (the first was 20261003.2610), the built-in browser in the desktop app stopped working. Opening the Browser surface shows the tab and the local-servers list, but typing a URL and pressing Enter does nothing: the field clears back to "Search or enter URL" and no page ever loads, with no error anywhere in the UI. Agents in the thread cannot use the browser either — the desktop browser tools fail with [browser.disconnected] No desktop browser is connected to this session even though the desktop app is open on the thread. Restarting the app and rebooting the machine did not help. A screen recording shows the pane accepting google.com and then doing nothing.

Diagnosis

Agent-assisted investigation on the machine:

  • Reproduced the agent-side failure live: browser.tabs.list and browser.tabs.open both fail with [browser.disconnected] No desktop browser is connected to this session. Open this session in the desktop app and wait for it to connect. while the desktop app is open on the thread. It persists across an app restart and a full reboot.
  • Differential: server-hosted preview tabs still work. preview_open + preview_navigate to https://example.com succeeds and the tab reports the URL and title correctly (the tab was closed afterwards). So the server browser host is fine; the desktop browser host is the one that does not attach.
  • No server-child*.log* files exist. Per the triage docs those are written only when a desktop backend crashes or fails to start, so the desktop backend is not crashing.
  • server.trace.ndjson shows orchestration-v2.* startup spans; no browser-related error spans were found.
  • The reporter first hit this after updating to the V2 nightlies; it worked before.

Steps to reproduce

  1. Desktop app (Windows) connected to a T3 server on WSL2.
  2. Open a thread, open the Browser surface ("Open a surface" → Browser).
  3. Type google.com and press Enter. The field clears back to the placeholder; the pane stays on the "Local servers" list; nothing loads and no error is shown. Clicking a listed local server does nothing either.
  4. In the same thread, have an agent call browser.tabs.list. It fails with [browser.disconnected] No desktop browser is connected to this session.
  5. Quit and reopen the app, reboot the machine: unchanged.

Version

0.0.46-nightly.20261007.2787

Environment

Windows 11 host + WSL2 (kernel 6.18.33.2-microsoft-standard-WSL2); T3 server on the WSL side (linux x64); the desktop app connects to it. Environment label P7-DESKTOP.

Evidence

shell
1agent -> browser.tabs.list
2[browser.disconnected] No desktop browser is connected to this session. Open this session
3in the desktop app and wait for it to connect.
4
5agent -> preview_open + preview_navigate https://example.com (server-hosted tab)
6available: true, url: https://example.com/, title: "Example Domain"
7
8$ ls ~/.t3/userdata/logs/server-child*
9none
10$ grep -ic browser ~/.t3/userdata/logs/server.trace.ndjson
110

Screen recording of the dead pane: will be attached in a comment.

  • #16639 (address bar cannot search; silent failure) and #16638 (environment-hosted tabs blocked as HeadlessChrome) — a fix for this batch is in review: #16963.
  • #16640, #16641 — other environment-hosted tab bugs.
  • #15965 (closed unmerged) documents the desktop host dropping batched requests and being disconnected by the broker; #11223 (closed) is about desktop snapshots.
  • None of these covers a desktop browser host that does not attach, with the pane silently doing nothing on the V2 nightlies.

Fix applied or workaround

None yet — investigation only; nothing was changed on the machine.

Filed by

opencode (deepseek-v4.1-flash) in a T3 Code agent session — agent-assisted triage; structure follows the via-triage template.

Note

Grok responding on behalf of Julius.

Thanks for the detailed report and the server-tab differential. I traced this in code on main (ff3a04a8) but haven't reproduced it in a running app. I found a specific path that matches every symptom, if your desktop app runs in WSL-only mode, meaning the WSL backend is the app's primary backend rather than a second backend next to the Windows one. Can you confirm which setup you have?

Likely mechanism (WSL-only primary)

  1. The WSL primary never gets the desktop browser channel. The Windows primary is spawned with browser fds 6/7 (DesktopBackendConfiguration.ts L574-L575). In WSL-only mode, resolvePrimary uses buildWslPrimaryConfig instead. That reuses the primary's port and identity ("primary-aware code paths … keep working"), but its bootstrap has no browser fds and is delivered over stdin (L773), because "wsl.exe drops additional file descriptors" (DesktopBackendManager.ts L504-L507). On the server, that makes DesktopBrowserChannel available: false. desktopRenders then returns false, and every tab runs on the server's own headless page. That's why preview_open/preview_navigate work for agents.
  2. The client still renders those tabs natively. Every server advertises serverBrowser: true, so new tabs get runtime: "server". rendersServerTabNatively only checks Electron, runtime === "server" and environment == primary. It can't tell that this server has no desktop channel. So ElectronBrowserHost mounts a local <webview> marked serverDriven, and PreviewView treats the tab as native instead of streaming it.
  3. URL entry goes to that local webview, and the server never hears about it. Submitting calls previewBridge.navigate on the desktop webview. For serverDriven tabs the bridge skips reporting status (usePreviewBridge.ts L84), because the server normally sees navigation over the channel. Here there's no channel, so the server's snapshot stays Idle. Then:
    • The address bar blurs on submit (PreviewChromeRow.tsx L106-L112) and shows the snapshot URL (L183, PreviewView.tsx L194). That URL is "", so you see the placeholder again.
    • Idle keeps the empty state (Local servers) on screen, and the webview slot isn't rendered.
    • Local-server clicks go through the same navigate path. No error appears anywhere, so it fails silently.

Agents' preview_* tools and your panel end up on two different pages. That's the same class of split as #16901, except there it happens after a 10s attach timeout, and here it happens on every tab.

Not from T3 Code: the [browser.disconnected] No desktop browser is connected to this session string and the browser.tabs.list/browser.tabs.open tool names don't appear anywhere on main. T3 Code's agent browser tools are preview_*. That error most likely comes from the agent harness's own browser tooling, so it isn't evidence about the T3 desktop host.

Timing: this path came in with #15328 (merged Oct 6). The first nightly that includes it is 0.0.46-nightly.20261006.2735. The V2 nightlies you mention (20261003.2610 through 20261005.2702) don't have it. If the pane really was dead on those builds, something else is also involved. Please say whether it worked on 20261005.2702.

Next steps / fix options (unverified)

  • Make "native vs. stream" depend on whether the server actually has the desktop channel. For example, the server could advertise a capability only when DesktopBrowserChannel.available, and rendersServerTabNatively could require it. Without it, a WSL-only primary would stream its headless tabs like any other environment.
  • Alternatively, have the desktop mark a stdin-bootstrapped (WSL) primary as not natively rendered.
  • Longer term, carry the browser channel over a transport that survives wsl.exe, such as a loopback socket, so WSL tabs render natively too.
  • Possible workaround (untested): turn off WSL-only mode and add WSL as a second backend next to the Windows one. Its tabs should then stream instead of using the local webview.

To confirm

  • Is the desktop app in WSL-only mode? Does the WSL environment show as the primary/"This machine" environment in Settings → Connections?
  • Did the Browser panel work on nightly 20261005.2702 and stop on 20261006.2735 or later?

Related

  • Open PR #16963 fixes #16638/#16639/#16640 in the environment-hosted browser, but it doesn't touch the native-vs-stream decision or the desktop browser channel. I don't expect it to fix this.
  • None of the other open PRs that touch these files change this path either (#16956, #16961, #16620, #16626, #16835).
  • #16901 is the same webview/headless split, with a timing trigger.
  • #16639 is about search text in the address bar; google.com already resolves as a URL. #16641 is input blocking in streamed tabs. #15965 (closed) was the old preview-automation host batching. #11223 (closed) is desktop snapshots. All of these are distinct.

Confirming from the machine — thanks for the trace, it matches what we see:

  • WSL-only mode: yes. The app's only backend runs in WSL (wsl.exe -d archlinux --exec … t3 --bootstrap-fd 0); there is no Windows-side backend process (only the Electron UI processes), and the app ships wsl-runtime.tar.gz in its resources.
  • Timing: not sure, unfortunately. My first clear repro is on 20261007.2787, and I can't confirm whether the Browser surface worked on 20261005.2702 — my impression was that it broke with the V2 nightlies, but I can't pin the build.
  • The pane behavior matches your steps: on submit the address bar shows the empty snapshot URL (placeholder again) and the "Local servers" empty state stays; no error anywhere. The app created local preview webview partitions on the Windows side today (t3code-preview-*), consistent with your step 2.
  • Noted on [browser.disconnected] / browser.tabs.* — that's the agent harness, not T3 Code; I'll disregard it as evidence about the desktop host.

Happy to test a build with the capability-gated fix, or report back if the workaround (WSL as a second backend next to Windows) changes anything.

Note

Grok responding on behalf of Julius.

Carrying over from #19977 (closed as a duplicate). It's the agent-facing side of this same never-attached state, reported on Windows 11 desktop + WSL-only server, 0.0.46-nightly.20261008.2819, opencode provider threads:

  • browser.tabs.open returns [browser.disconnected] No desktop browser is connected to this session. … Then call browser.tabs.list({}). Repeating browser actions while disconnected will not help.
  • browser.tabs.list({}), the probe that message points to, fails with the same text, so agents get no status back.
  • Per the reporter, agents abandon the browser after ≤2 attempts and fall back to curl, without re-probing after the desktop app attaches.

When this is fixed, the never-attached wording/probe semantics should be checked alongside the attach itself.

Synced from GitHub

Confirming from a second install, with fresh evidence — the failure has persisted Oct 7 through today (Oct 15) here. This machine runs desktop build 0.0.46-nightly.20261008.2819 (a later nightly than the reporter's 20261007.2787; verified as service.version in server.trace.ndjson), and the failure reproduces live on it today.

Same shape as the reporter's machine: Windows desktop app with the T3 server running inside WSL as the only backend (~/.t3/wsl-runtime), and the server's headless browser host (chrome-headless-shell, user-data-dir under ~/.t3/userdata/server-browser/) keeps running throughout. The T3 desktop pane remains affected, and the separate upstream OpenCode browser tools report no current attachment; those observations do not establish a shared cause.

From the thread transcript DB on this machine, Oct 7–15:

1sessions that attempted the agent browser tools: 18
2 (opencode-go: glm-5.3-flash, deepseek-v4.1-flash, mimo-v2.6-pro)
3total tool-call attempts: 34
4returned "[browser.disconnected] No desktop browser...": 30
5other errors (invalid file:// URL, unknown tool names): 4
6successes: 0

Still reproducible live on Oct 15: upstream OpenCode's browser.tabs.list fails even after waiting per the error's own guidance, while T3's server-hosted preview tabs render normally. The latter is consistent with the server/pane differential in the maintainer analysis; the OpenCode tool error is a separate observation, not evidence that T3's desktop browser channel caused it.

One correction for the triage picture: the agent-side browser.tabs.* tools are upstream OpenCode's, not this repo's — the exact [browser.disconnected] strings live in anomalyco/opencode's packages/plugin-browser/src/connection.ts, and OpenCode itself uses the "Review pane"/localhost vocabulary in its tool descriptions. Those names do not establish a T3-specific bridge. OpenCode's "no current attachment" branch checks for absence from its session browsers map, including after removal on disconnect; it does not prove a browser never attached and is distinct from a connection closing during an in-flight request. As the earlier maintainer analysis noted, these OpenCode errors are not evidence about T3's desktop host, and #17009 is environmental context rather than a proven cause of OpenCode's missing RPC attachment. The "will not help" wording and a nonthrowing browser.tabs.list({}) connection-status probe are the upstream feature request, so I've filed:

  • anomalyco/opencode#55795 — no current attachment error semantics + a nonthrowing tabs.list status probe (upstream)
  • this issue remains the place to investigate T3's desktop pane/browser-channel symptoms; a causal link to OpenCode's attachment failure is unconfirmed

Filed by: opencode (glm-5.3-flash) in a T3 Code agent session.

Synced from GitHub

I hit this on two Windows machines this week. To answer your two questions:

  • Setup: both desktops run in WSL-only mode. The WSL backend is the primary and only backend, started as ~/.t3/wsl-runtime/sha256-…/t3 --bootstrap-fd 0, version 0.1.0. I don't run anything on the Windows side except the UI.
  • Timing: it started roughly two weeks ago, which fits #15328. I can't tell you which nightly it first appeared in. It didn't happen in every session: some still shared the panel's page normally. My guess is that those ran on non-primary environments, but I haven't checked.

What I saw myself, on 0.1.0: I signed in to an admin console in the Browser panel. The agent, targeting the same tab id, kept getting the site's login page, and preview_status reported visible: false. The agent's tabs clearly ran on the WSL side. Its first preview_open failed with "This host is missing libraries T3's browser needs (libasound.so.2)" until I ran t3 browser setup inside WSL.


Everything below this line was written by an AI agent (Claude Code) at my request. I haven't reviewed it by hand. It follows your first fix option and revives the closed #17926 on current main (13aa882c). Treat it as a sample you can discard at any time. Checks the agent ran on my machine (Ubuntu 26.04 in WSL):

  • New tests in ServerEnvironment.test.ts (9/9) and previewRuntime.test.ts (5/5) pass. The full web unit project passes (6,915 tests).
  • tsc-rs --noEmit passes for contracts, server and web. Lint and vp fmt pass on the changed files.
  • Not run: the browser-project tests (Playwright doesn't support Ubuntu 26.04), and any end-to-end check in a Windows desktop build.
Sample diff (7 files, +94/−5)
diff
1diff --git a/apps/server/src/environment/ServerEnvironment.test.ts b/apps/server/src/environment/ServerEnvironment.test.ts
2index b17668002..dc667037a 100644
3--- a/apps/server/src/environment/ServerEnvironment.test.ts
4+++ b/apps/server/src/environment/ServerEnvironment.test.ts
5@@ -312,6 +312,43 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => {
6 }),
7 );
8
9+ it.effect("advertises desktopBrowser only with both desktop browser channel fds", () =>
10+ Effect.gen(function* () {
11+ const fileSystem = yield* FileSystem.FileSystem;
12+ const baseDir = yield* fileSystem.makeTempDirectoryScoped({
13+ prefix: "t3-server-environment-desktop-browser-test-",
14+ });
15+ const serverConfig = yield* makeServerConfig(baseDir);
16+ yield* fileSystem.makeDirectory(serverConfig.stateDir, { recursive: true });
17+
18+ const describeWith = (overrides: Partial<ServerConfig.ServerConfig["Service"]>) =>
19+ Effect.gen(function* () {
20+ const serverEnvironment = yield* ServerEnvironment.ServerEnvironment;
21+ return yield* serverEnvironment.getDescriptor;
22+ }).pipe(
23+ Effect.provide(
24+ ServerEnvironment.layer.pipe(
25+ Layer.provide(ServerSecretStore.layer),
26+ Layer.provide(ServerConfig.layer({ ...serverConfig, ...overrides })),
27+ ),
28+ ),
29+ );
30+
31+ const withChannel = yield* describeWith({
32+ mode: "desktop",
33+ desktopBrowserFd: 6,
34+ desktopBrowserControlFd: 7,
35+ });
36+ expect(withChannel.capabilities.serverBrowser).toBe(true);
37+ expect(withChannel.capabilities.desktopBrowser).toBe(true);
38+
39+ // A WSL primary is bootstrapped over stdin, which carries no browser fds.
40+ const wslPrimary = yield* describeWith({ mode: "desktop" });
41+ expect(wslPrimary.capabilities.serverBrowser).toBe(true);
42+ expect(wslPrimary.capabilities.desktopBrowser).toBeUndefined();
43+ }),
44+ );
45+
46 it.effect("structures persisted environment id filesystem failures", () =>
47 Effect.gen(function* () {
48 const fileSystem = yield* FileSystem.FileSystem;
49diff --git a/apps/server/src/environment/ServerEnvironment.ts b/apps/server/src/environment/ServerEnvironment.ts
50index cef5d9358..6f908987b 100644
51--- a/apps/server/src/environment/ServerEnvironment.ts
52+++ b/apps/server/src/environment/ServerEnvironment.ts
53@@ -264,6 +264,10 @@ export const make = Effect.gen(function* () {
54 : {}),
55 ...(desktopAppUpdate ? { desktopAppUpdate: true } : {}),
56 serverBrowser: true,
57+ ...(serverConfig.desktopBrowserFd !== undefined &&
58+ serverConfig.desktopBrowserControlFd !== undefined
59+ ? { desktopBrowser: true }
60+ : {}),
61 },
62 };
63
64diff --git a/apps/web/src/browser/ElectronBrowserHost.tsx b/apps/web/src/browser/ElectronBrowserHost.tsx
65index 351b819b6..6704ccb98 100644
66--- a/apps/web/src/browser/ElectronBrowserHost.tsx
67+++ b/apps/web/src/browser/ElectronBrowserHost.tsx
68@@ -14,6 +14,7 @@ import { primaryEnvironmentIdAtom } from "~/state/primaryEnvironment";
69 import { isElectron } from "~/env";
70 import { useClientSettingsHydrated } from "~/hooks/useSettings";
71 import { useActivePreviewSessions } from "~/previewStateStore";
72+import { useEnvironmentHasDesktopBrowser } from "~/state/entities";
73 import { previewEnvironment } from "~/state/preview";
74 import { useEnvironmentScope } from "~/state/session";
75 import { useAtomCommand } from "~/state/use-atom-command";
76@@ -29,6 +30,7 @@ import { previewRuntimeTabId } from "./previewRuntimeTabId";
77 export function ElectronBrowserHost() {
78 const previewByThreadKey = useActivePreviewSessions();
79 const primaryEnvironmentId = useAtomValue(primaryEnvironmentIdAtom);
80+ const primaryHasDesktopBrowser = useEnvironmentHasDesktopBrowser(primaryEnvironmentId);
81 const sessions = Object.entries(previewByThreadKey).flatMap(([threadKey, previewState]) => {
82 const threadRef = parseScopedThreadKey(threadKey);
83 return threadRef
84@@ -36,7 +38,12 @@ export function ElectronBrowserHost() {
85 .filter(
86 (snapshot) =>
87 snapshot.runtime !== "server" ||
88- rendersServerTabNatively(threadRef.environmentId, primaryEnvironmentId, snapshot),
89+ rendersServerTabNatively(
90+ threadRef.environmentId,
91+ primaryEnvironmentId,
92+ primaryHasDesktopBrowser,
93+ snapshot,
94+ ),
95 )
96 .map((snapshot) => ({
97 threadRef,
98diff --git a/apps/web/src/browser/previewRuntime.test.ts b/apps/web/src/browser/previewRuntime.test.ts
99index 951b52e90..a1c8f379f 100644
100--- a/apps/web/src/browser/previewRuntime.test.ts
101+++ b/apps/web/src/browser/previewRuntime.test.ts
102@@ -7,16 +7,21 @@ const state = vi.hoisted(() => ({
103 serverBrowser: new Set<string>(),
104 }));
105
106-vi.mock("~/env", () => ({ isElectron: false }));
107+vi.mock("~/env", () => ({ isElectron: true }));
108 vi.mock("~/previewStateStore", () => ({ isPreviewSupportedInRuntime: () => state.desktop }));
109 vi.mock("~/rpc/atomRegistry", () => ({ appAtomRegistry: { get: () => state.primary } }));
110 vi.mock("~/state/primaryEnvironment", () => ({ primaryEnvironmentIdAtom: {} }));
111 vi.mock("~/state/entities", () => ({
112 readEnvironmentSupportsServerBrowser: (id: string) => state.serverBrowser.has(id),
113+ useEnvironmentHasDesktopBrowser: () => false,
114 useEnvironmentSupportsServerBrowser: () => false,
115 }));
116
117-import { alternatePreviewRuntime, previewRuntimeFor } from "./previewRuntime";
118+import {
119+ alternatePreviewRuntime,
120+ previewRuntimeFor,
121+ rendersServerTabNatively,
122+} from "./previewRuntime";
123
124 const local = "local" as EnvironmentId;
125 const remote = "remote" as EnvironmentId;
126@@ -58,3 +63,13 @@ describe("alternatePreviewRuntime", () => {
127 expect(alternatePreviewRuntime(remote, null, true, { runtime: "server" })).toBeNull();
128 });
129 });
130+
131+describe("rendersServerTabNatively", () => {
132+ it("draws the primary's server tabs natively only when it holds the desktop browser channel", () => {
133+ expect(rendersServerTabNatively(local, local, true, { runtime: "server" })).toBe(true);
134+ // A WSL-only primary has no channel, so a local <webview> would never reach its server.
135+ expect(rendersServerTabNatively(local, local, false, { runtime: "server" })).toBe(false);
136+ expect(rendersServerTabNatively(remote, local, true, { runtime: "server" })).toBe(false);
137+ expect(rendersServerTabNatively(local, local, true, {})).toBe(false);
138+ });
139+});
140diff --git a/apps/web/src/browser/previewRuntime.ts b/apps/web/src/browser/previewRuntime.ts
141index 3d8e1d0a1..68e496a69 100644
142--- a/apps/web/src/browser/previewRuntime.ts
143+++ b/apps/web/src/browser/previewRuntime.ts
144@@ -7,6 +7,7 @@ import { appAtomRegistry } from "~/rpc/atomRegistry";
145 import { primaryEnvironmentIdAtom } from "~/state/primaryEnvironment";
146 import {
147 readEnvironmentSupportsServerBrowser,
148+ useEnvironmentHasDesktopBrowser,
149 useEnvironmentSupportsServerBrowser,
150 } from "~/state/entities";
151
152@@ -41,16 +42,20 @@ export function usePreviewAvailable(environmentId: EnvironmentId | null): boolea
153 return isPreviewSupportedInRuntime() || serverBrowser;
154 }
155
156+// A WSL primary has no desktop browser channel, so its server never sees a
157+// local `<webview>`'s navigation; the desktop streams its tabs instead.
158 export function rendersServerTabNatively(
159 environmentId: EnvironmentId,
160 primaryEnvironmentId: EnvironmentId | null,
161+ primaryHasDesktopBrowser: boolean,
162 snapshot: Pick<PreviewSessionSnapshot, "runtime"> | null | undefined,
163 ): boolean {
164 return (
165 isElectron &&
166 snapshot?.runtime === "server" &&
167 primaryEnvironmentId !== null &&
168- environmentId === primaryEnvironmentId
169+ environmentId === primaryEnvironmentId &&
170+ primaryHasDesktopBrowser
171 );
172 }
173
174@@ -58,5 +63,12 @@ export function useRendersServerTabNatively(
175 environmentId: EnvironmentId,
176 snapshot: Pick<PreviewSessionSnapshot, "runtime"> | null | undefined,
177 ): boolean {
178- return rendersServerTabNatively(environmentId, useAtomValue(primaryEnvironmentIdAtom), snapshot);
179+ const primaryEnvironmentId = useAtomValue(primaryEnvironmentIdAtom);
180+ const primaryHasDesktopBrowser = useEnvironmentHasDesktopBrowser(primaryEnvironmentId);
181+ return rendersServerTabNatively(
182+ environmentId,
183+ primaryEnvironmentId,
184+ primaryHasDesktopBrowser,
185+ snapshot,
186+ );
187 }
188diff --git a/apps/web/src/state/entities.ts b/apps/web/src/state/entities.ts
189index b456752ca..80b24d87e 100644
190--- a/apps/web/src/state/entities.ts
191+++ b/apps/web/src/state/entities.ts
192@@ -267,6 +267,15 @@ export function readEnvironmentSupportsServerBrowser(environmentId: EnvironmentI
193 );
194 }
195
196+/** Whether the desktop app holds this environment's browser channel and so can draw its server tabs natively. */
197+export function useEnvironmentHasDesktopBrowser(environmentId: EnvironmentId | null): boolean {
198+ const configs = useServerConfigs();
199+ return (
200+ environmentId !== null &&
201+ configs.get(environmentId)?.environment.capabilities.desktopBrowser === true
202+ );
203+}
204+
205 export function readEnvironmentSupportsTitleRegeneration(environmentId: EnvironmentId): boolean {
206 return (
207 appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities
208diff --git a/packages/contracts/src/environment.ts b/packages/contracts/src/environment.ts
209index b78cf0f99..3c8d3c7e2 100644
210--- a/packages/contracts/src/environment.ts
211+++ b/packages/contracts/src/environment.ts
212@@ -149,6 +149,11 @@ export const ExecutionEnvironmentCapabilities = Schema.Struct({
213 "server"`) and streams them over `/api/preview-stream`. Clients
214 without a local browser runtime open server tabs here. */
215 serverBrowser: Schema.optionalKey(Schema.Boolean),
216+ /** The desktop app that launched this server holds its browser channel, so
217+ it can draw this server's tabs in its own `<webview>`. Absent on WSL
218+ backends, whose bootstrap cannot carry the channel fds, and on servers
219+ no desktop app launched; the desktop app streams their tabs instead. */
220+ desktopBrowser: Schema.optionalKey(Schema.Boolean),
221 });
222 export type ExecutionEnvironmentCapabilities = typeof ExecutionEnvironmentCapabilities.Type;
223

I'm happy to test a release that contains a fix on both machines and report back here.

Claude Code drafted and posted this comment at my request. The observations above the line are from my own sessions.

Synced from GitHub

Checked current builds, since the last confirmations were on older ones. Still not fixed today:

  • Newest nightly 0.1.1-nightly.20261018.3335 and main (952921f3): rendersServerTabNatively in apps/web/src/browser/previewRuntime.ts (L44-55) still treats a primary environment's server tab as natively rendered, with no desktop-channel check.
  • apps/server/src/environment/ServerEnvironment.ts still advertises no desktopBrowser capability, and DesktopBrowserChannel.ts still returns available: false without the browser fds.

Another WSL-only install, on 0.1.1-nightly.20261018.3285, shows the same shape: the backend runs as t3 --bootstrap-fd 0 with no browser fds, so the desktop browser can never attach.

On direction: option 1 now has three implementations to choose from, #17926 (closed by its author), JerkyTreats/t3code 34b9342f, and the sample diff above. Which option do you want? I can open a focused PR for the one you pick and test candidate desktop builds on this setup.

Written from a T3 Code agent session on the WSL-only install above; the checks ran there.

Synced from GitHub

Still reproducible on 0.1.2-nightly.20261021.3486, using WSL as the primary and only backend.

Entering https://example.com in the sidebar Browser pane clears the address bar and shows no page. However, collapsing the sidebar reveals a small floating browser preview displaying the loaded example.com page. The page appears to load successfully, while the expanded pane fails to display it. Screenshot attached.

https://github.com/user-attachments/assets/28cdabdb-895f-42c6-9f4d-167e1f6ad37c

Synced from GitHub

Still reproducible on stable 0.1.2 (also seen on 0.1.0) with a WSL-only backend. I re-checked the code on main at 62245d8e (2026-10-25). The WSL bootstrap still sets no desktopBrowserFd / desktopBrowserControlFd, DesktopBackendManager.ts still attaches extra fds only for bootstrapDelivery: "fd3", and DesktopBrowserChannel still comes up with available: false.

Two details I haven't seen in this thread yet:

  • The panel shows the desktop's own copy of the tab while the agent drives the headless copy in WSL. The panel's address bar showed the agent tab's URL (a sign-in page) on top of the desktop copy's signed-in content. Nothing in the UI shows that these are two different pages.
  • The panel has no Take control button, because it isn't streaming the server tab. So I couldn't sign in on the agent's behalf.
text
1preview_open {"url": "http://app.localdev.test:8080/dashboard", "reuseExistingTab": false, "open": true}
2-> {"visible": false, "tabId": "tab_4_...", "control": {"owner": "agent", "generation": 0}, ...}
3preview_snapshot -> URL http://auth.localdev.test:8080/login?redirectUrl=..., title "Sign in"
4Panel at the same time: signed-in page, address bar showing the login URL, no Take control.
5Server-browser profile cookie DB: 0 cookies for the site. The desktop session was signed in.

Workaround that worked for me: the agent opened a tab with profileId: "Default" and typed the login itself once. The cookies stayed in ~/.t3/userdata/server-browser/profiles/default, and later agent tabs on that profile were signed in. The catch is that the password has to go through the chat, because request_secret refs can't be passed to preview_type.

Environment: Windows 11 (build 10.0.26300), WSL 2.7.12, Ubuntu 24.04 (NAT networking), Claude Code 2.1.303.

Investigated by Claude (opus-5.5) via Claude Code, on the local machine and the T3 source.

Synced from GitHub

1. Summary

On T3 Code 0.1.5 (stable), Windows desktop app, the embedded browser control session fails to attach. Every browser.* tool call (OpenCode browser plugin driving the desktop app's Chromium) returns:

1[browser.disconnected] No desktop browser is connected to this session.
2Open this session in the desktop app and wait for it to connect.
3Then call browser.tabs.list({}). Repeating browser actions while disconnected will not help.

This reproduces 100% of attempts, on a fresh app start, after full OS reboot, and with WSL environments shut down (Windows standalone). The same build's browser works on the user's WSL environment.

2. Environment

ItemValue
OSMicrosoft Windows 10, build 10.0.19045 (19045)
AppT3 Code (Beta) 0.1.5 stable (from desktop-last-started-version)
Server processNative Windows process T3 Code (Beta).exe (server PID in server-runtime.json), listening 0.0.0.0:3773, serverVersion: 0.1.5
Configdesktop-settings.json: serverExposureMode: "network-accessible", tailscaleServeEnabled: true
WSLno t3 serve process running at time of reproduction (Windows standalone)

3. Steps to reproduce

  1. Launch T3 Code desktop on Windows.
  2. Open a session in the desktop app.
  3. Attempt any browser.* call (e.g., browser.tabs.open({ url: "https://www.google.com" })).
  4. Observe the [browser.disconnected] error every time.

Also reproduced with the server-hosted T3 preview tooling: the preview browser loads the page successfully (T3's own server-hosted browser), but the desktop-embedded browser bridge fails.

4. Observed behavior

  • browser.tabs.open → Error: [browser.disconnected] … (always).
  • Trace shows every attempt failing at the same internal step:
1PreviewManager.initializeControlSession (WebContents 2 / 3)
2 → initializeDebugger.defaultBackground
3 → [cause] Error: target closed while handling command

The DevTools target is closed the instant a command lands the WebContents dies during debugger attach.

  • DesktopBrowserChannel availability lines never appear in the trace (matches "server advertises no desktop browser channel" / DesktopBrowserChannel.available: false symptom).

Earliest failure found: 2026-10-28 ~15:47Z

Sample failure span (current log, trimmed):

json
1{"type":"effect-span","name":"PreviewManager.ensureControlSession",
2 "exit":{"_tag":"Failure",
3 "cause":"PreviewOperationError: Desktop preview operation failed: initializeDebugger.defaultBackground (WebContents 3)
4 at PreviewManager.initializeControlSession (main.cjs:111432:7)
5 at PreviewManager.createControlSession (main.cjs:111433:6)
6 at PreviewManager.ensureControlSession (main.cjs:112299:26)
7 at PreviewManager.restoreControlSession (main.cjs:112329:10)
8 ..."}}

6. What has been tried (all still fails)

  • Full app quit + relaunch; backend PID changes each restart (confirmed server restarts).
  • Full OS reboot (still fails).
  • Closing DevTools/"inspect element" tabs; clearing/backing-up browser profile dir ...\userdata\server-browser (empty).
  • Shutting down all WSL environments (Windows standalone) - still fails.
  • Same app build on WSL environment works (browser attaches fine).

TL;DR

Windows 10 build 10.0.19045 (19045), desktop app native server (0.0.0.0:3773), WSL shut down (no t3 serve running). Every browser.* call returns the exact [browser.disconnected] error. Trace shows initializeDebugger.defaultBackground (WebContents 2/3) → Error: target closed while handling command on every attempt (Earliest failure found: 2026-10-28 ~15:47Z). DesktopBrowserChannel availability never appears in the trace.

Already tried: full restart, OS reboot, clearing browser profile dir, closing DevTools tabs, disabling WSL environments. Same build's browser works fine on the WSL environment. Only the Windows desktop browser channel fails.

Synced from GitHub

Follow-up: Just to clarify, I don't think this is a T3 Code issue. I was on OpenCode v2.2.4 and the browser tools kept failing with [browser.disconnected]. When I downgraded to OpenCode v1.19.4, the same tools worked fine on the same T3 0.1.5 (stable) setup. So it looks like a regression somewhere between Opencode v1.19.4 and v2.2.4.