Repository navigation
Description
I had three Cursor CLI threads running in T3. Extra T3 instances started opening, and the threads showed:
Provider session did not survive a server restart. Send a new message to continue.
But the agents were still working. The logs show fresh tool calls after that error in all three threads, with two continuing for more than ten minutes. This made it hard to know whether anything had actually stopped or whether sending “continue” would make things worse.
What triggered it
The first extra instance came from an agent running T3’s generated agent-device help command during iOS UI verification. The command’s output contains a desktop startup log, with the same run ID as the extra instance in T3’s desktop trace.
Later, agent-device version and agent-device install also launched extra instances. Two threads are confirmed to have triggered this; the third was affected without calling agent-device itself.
What the investigation found
The generated launcher on my machine contains this:
#!/bin/sh
exec '/Applications/T3 Code (Nightly).app/Contents/MacOS/T3 Code (Nightly)' '<T3_STATE>/device/bin/agent-device-launcher.mjs' "$@"It invokes the Electron app without explicitly setting ELECTRON_RUN_AS_NODE=1. In this build, resolveNodeExecutable returns the Electron executable, and the helper ends up opening T3 instead of running the device CLI.
The extra app starts a backend on another port using the same T3 state. During startup, reconcileProviderSessions sees no providers in that new process and treats the original server’s sessions as lost. The original Cursor workers keep running.
An isolated test against the shipped source confirms that this recovery path marks the sessions as stopped/error without calling stopSession or interruptTurn. The continued execution is separately confirmed by the real thread logs. This wasn’t a full reproduction with two live servers.
I’d expect the device helper to return CLI output without opening another app, and a second backend to be prevented from changing another running backend’s session state.
Environment
- T3 Code:
0.0.43-nightly.20260921.2044, commit5781b5240bd5 - macOS 27.0 (
26A428) - Installed Cursor CLI:
2026.09.18-9a7762b - Incident: September 21, 2026
PR #12241 describes the same failure and was closed without merging. It looks relevant here because the generated launcher in this build still has the problem.
I have a sanitized timeline, command/startup excerpts, and the isolated test ready to share. The investigation hasn’t established why the desktop single-instance guard allowed these launches.
Activity
juliusmarminge commentedon Sep 21, 2026
Triage
Confirmed against current
main. This is a real high-severity desktop/device bug, not a one-off Nightly glitch.Running T3's generated
agent-devicehelper from a Cursor CLI thread can start another desktop instance. That instance binds a fallback port against the same T3 home, and startup reconciliation marks the original server's live sessions as lost while the Cursor workers keep running.What the code does today
On desktop,
resolveNodeExecutable(packages/shared/src/nodeRuntime.ts) returns the Electron binary when the host is not a Node SEA. That is intentional: other desktop spawners already setELECTRON_RUN_AS_NODE=1(backend, snapshot helpers, Antigravity).ensureAgentDeviceShim(apps/server/src/device/AgentDeviceShim.ts) does not. It still writes:So
agent-device help|version|installstarts a full GUI before the launcher can run as Node. Provider env inapps/server/src/provider/Layers/ProviderService.tsalso omits the flag.The extra app then hits
resolveDesktopBackendPort(apps/desktop/src/app/DesktopApp.ts), which still scans3773through65535. The new backend opens the same home and runsreconcileProviderSessions(apps/server/src/serverRuntimeStartup.ts). In that processlistSessions()is empty, so the original threads look orphaned and are settled tostopped/errorviathread.session.setonly — nostopSessionorinterruptTurn. That matches both the isolated test and the "fresh tool calls after the error" logs.The third thread being marked lost without calling
agent-deviceis expected: reconciliation is process-wide.Single-instance lock
Still unexplained, but the code path is narrow.
DesktopClerktakes Electron's lock (scoped touserData) and quits secondaries before bootstrap. A second backend binding another port means those launches were treated as primary. Directexecof the Nightly binary with a.mjsargument is the likely bypass. Not a blocker for the shim fix.Related, not duplicates
agent-device. Keeping this issue separate.No open PR currently attributes or fixes the
agent-deviceshim path.Recommended fix
ELECTRON_RUN_AS_NODE=1in the POSIX wrapper, the Windows.cmd, thelauncher.mjsspawn env, andagentDeviceEnvironment. Add the desktop-executable regression inAgentDeviceTarget.test.ts.reconcileProviderSessionsshould not settle sessions owned by another live backend.Sanitized timeline / isolated test welcome as attachments. A full two-server live repro is not required to accept this.
Workaround: do not run
agent-devicefrom provider threads until a build ships the flag. If an extra instance already appeared, do not send "continue" on threads that still look active in logs; quit the extra app and treat the original workers as still running.