Repository navigation
Description
Note
Claude Opus 5.5 in Claude Code, writing on behalf of @rxsto. The measurements are from the reporter's server on 2026-11-04.
Before submitting
- I searched existing issues and did not find a duplicate. Related but different: Server event loop freezes 6–28 s on a normal-sized database (5.9 GB), so remote devices lose Send #26224 and SQLite writes run on the server's event loop, so one slow transaction disconnects every client #30065 (event-loop stalls), History-wide reads saturate the single SQLite reader on large databases (thread switches take 12-50 s) #24895 (reader saturation), and [Bug]: Every git spawn blocks the server's event loop for longer as its memory grows (fork on Linux) #21289 (spawn cost on Linux). None of them covers where Cursor sessions run.
- I included enough detail to reproduce or investigate the problem.
Area
packages/provider-cursor, apps/server
What happens
The Cursor provider runs every Cursor agent inside the T3 server process. loadCursorSdk() loads @cursor/sdk into the server with createRequire. CursorAgentSdkRunner.open then calls Agent.create or Agent.resume in the same process. Claude, Codex, and Grok each run as a separate process, and the server only relays their events.
So each Cursor session's JavaScript, its protobuf transport, and the rg and cursorsandbox processes it starts all run on the server's main thread. That thread also serves HTTP, WebSocket, and T3 Connect traffic, and it routes SQLite reads to the reader workers. With many Cursor sessions open at once, the main thread has almost no idle time left.
Steps to reproduce
- On Linux, run
t3 servewith the Cursor provider signed in. - Start 24 Cursor children at once with
delegate_task, using providercursorand modelgrok-4.7with default options. Give each child a read-only task: list the files, rungit logandgit status, read up to 6 files, and reply with an 800-word summary. - While the children run, record a CPU profile of the server's main thread. I sent
SIGUSR1, then ranProfiler.startandProfiler.stopover CDP for 60 seconds.
If you reproduce this with the inspector, don't evaluate code in the server. I scheduled import('node:inspector').then((m) => m.close()) through Runtime.evaluate. The timer threw ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, nothing caught it, and the server exited with code 1.
Expected behavior
A Cursor session costs the server's main thread about what a Claude or Codex session costs, which is relaying its events. A wide Cursor fan-out does not delay turn starts, thread reads, or auth checks for the other threads on the server.
Actual behavior
The profile covers 61.3 s and 37,377 samples, taken 5 minutes after a server restart:
| Where the main thread spent its time | Share |
|---|---|
JavaScript in @cursor/sdk, @bufbuild/protobuf, and @connectrpc/connect |
21.6% |
spawn() called from Cursor SDK code |
6.1% |
| T3 server JavaScript, mostly Effect fiber scheduling and schema decoding | 54.8% |
spawn() called from T3 server code |
10.7% |
| Garbage collection | 3.4% |
| Idle | 3.4% |
I sampled main-thread CPU from /proc/<pid>/task/<pid>/stat every 5 seconds. It stayed between 58% and 96% while the 24 sessions ran. On the same process, with 2 non-Cursor threads running, it averaged 9% over 60 seconds.
The same thing happened earlier without a test. An orchestrator thread started 27 Cursor children between 14:50 and 14:55 UTC. The server traces for the following hour show:
ProviderTurnStartService.starttook a median of 46.6 s and up to 186 s.CursorAgentSdkSession.sendinsideCursorAdapterV2.startTurntook 1,033 s across 27 turn starts, about 38 s each.boot-service.logshowsAgentSkillsCursorRulesServiceloads of up to 32 s andcomputeGlobalCachewarnings of up to 14 s.- Lock waits made up 11,409 s of the 13,261 s that reader-connection queries spent in SQLite.
ProjectionStore.isNodeStored, a primary-key read, took a median of 146 ms in the server. The same read took 0.66 ms from a separate process on the same database. Each of the 4 reader worker threads had used about 3.5 minutes of CPU in 8.6 hours of uptime, so the reads were waiting on the main thread, not on SQLite. - Main-thread CPU was 63% to 81% in five 2-second samples during that hour.
Possible fix
Run @cursor/sdk off the main thread, in a worker thread or a child process that owns the SDK. The adapter would talk to it over a message channel, the way the other providers talk to their CLIs. A child process would also move the SDK's own rg and cursorsandbox spawns out of the server's large address space, which avoids the fork cost in #21289.
Environment
- T3 Code
0.1.7-nightly.20261104.4151(322f654),t3 serveas a systemd user service, with clients over T3 Connect - Bundled Node v26.12.1 with libuv 1.52.1, and
@cursor/sdk1.2.0 - Linux 7.0.0-42-generic, 8 vCPU AMD EPYC 9645, 15 GiB RAM
statev2.sqliteis 6.2 GB with 3 free pagesmainat 9e85538 has the same code insdk.tsandCursorAgentSdk.ts
I can attach both .cpuprofile files.
Activity
henryzhang03 commentedon Nov 7, 2026
Note
Claude Opus 5.5 in Cursor, writing on behalf of @henryzhang03. The measurements are from the reporter's Mac on 2026-11-06.
A second way to hit this: one long Cursor thread, with no fan-out running. Each message on that thread spends 15 to 21 s inside
agent.send()on the main thread, and the whole server stops responding for that time.Environment: T3 Code (Beta) 0.1.7 desktop app with its local server, macOS 27.2 on Apple silicon, 96 GB RAM, Cursor provider.
The thread: 12 runs, about 1,000 turn items, and 345 provider-native subagents left from earlier fan-outs. Their child threads hold about 19,800 more turn items.
CursorAgentSdkSession.sendfor messages sent on that thread (server.trace.ndjson, UTC):memory.heap_growth_bytesagent.send().event-loop-stalls.ndjsonmatches the sends.delayMaxMsrose from about 2.5 s earlier in the day to 20.7 s. The 20.7 s stall logged at 23:48:20 ends with the 21.5 s send that started at 23:47:58, and the 18.0 s stall logged at 23:43:52 ends with the 18.4 s send at 23:43:33. RSS in those records reached 5.7 GB.ws.rpc.orchestration.subscribeThread) took 25.5 s.Running the SDK off the main thread, as proposed above, would stop the server-wide freeze. The cost of each send also grows with this thread's history, so turn starts on a thread like this would stay slow unless that work is bounded too.
Earlier the same day, with 8 Cursor
delegate_taskchildren running, no server span completed for 4 minutes (17:22 to 17:26 UTC). HTTP requests to the server timed out after 15 s, and the desktop showed "T3 Code on did not answer". A native sample showed the main thread busy in JavaScript throughout. We did not take a JavaScript profile, so we can't say how much of that was SDK code, but it fits the fan-out case in this issue.