Repository navigation
perf(cursor): long command output streams without copying its tail per chunk - #26934
Conversation
…r chunk Trim the retained shell output only past 512 KiB instead of re-slicing the 256 KiB tail on every delta; the read site still applies the 256 KiB cap. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — The change reduces repeated copying while buffering Cursor command output, preserving the existing fallback output limit and live streaming behavior. The bounded increase in transient buffering presents no obvious material risk. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review. Walkthrough
Merge Risk:
|
|
@juliusmarminge ready for review. Cursor's shell-output handler re-copied its 256 KiB tail on every chunk once a command passed 256 KiB; it now trims in batches. 5 MB of output: 707 ms and 6.3 GB allocated down to 44 ms and 91 MB (16x faster, 69x fewer bytes), with an identical final output. Related tracker: #9661. The CI workflows on this fork PR show |
|
@juliusmarminge could you review this when you have a moment? Written by Claude Sonnet 5.5 via Claude Code in T3 Code. |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EvidenceRan real Cursor Grok 4.7 turns against independent fresh T3 servers on Linux / Node 26.12.1 / Cursor SDK 1.2.0. On each commit: run
CPU-only captures had allocation sampling disabled. Base source-position samples identify the concat+slice statement at adapter.ts:2115 in 624 of 664 handler self ticks. Allocation figures are statistical samples including collected objects. Handler self CPU decreased about 88%; slice allocations decreased over 99.9%. Both sides streamed exactly the same 1,048,576 characters, retained byte-identical 262,144-character command output, exited 0, and returned the actual final line The charts derive from raw profiles. Videos are explicitly labeled rendered playbacks of timestamped live RPC frames at original timing, not browser screen captures. Start, middle and completion frames of both encoded videos were inspected. Raw profiles, recordings data, transcripts and analysis scripts · Driver · Paced workload · Burst workload · Measurements and hashes · Methodology Not shown:
|
…r chunk (pingdotgg#26934) Co-authored-by: Julius Marminge <julius0216@outlook.com> (cherry picked from commit 6be1bd6)


Problem
Cursor's
shell-output-deltahandler kept the streamed command output withstreamedOutput = (streamedOutput + chunk).slice(-256 KiB). Once a command has printed more than 256 KiB, every chunk flattens and copies the whole 256 KiB tail, so a command that prints a few MB blocks the event loop and allocates gigabytes.Fix
packages/provider-cursor/src/server/adapter.ts: append each chunk with+=and trim back to 256 KiB only once the string passes 512 KiB (amortised O(1) per chunk). The only reader (outputTextin the tool-update path) now applies the sameslice(-256 KiB), so the visible output is unchanged and retained memory stays at most 512 KiB plus one chunk.Measurements
Real
makeCursorAdapterV2with a fake SDK; deltas are fired after the turn context exists. Fresh process per round, 5 interleaved rounds (baseorigin/mainvs this branch), median. Allocated bytes are summed heap growth between GCs (v8.GCProfiler).The sha1 of the final command output is identical before and after. What limits the new number: about 1.7 us and 3.6 KB per delta in
emitProviderEvent, id derivation, the event object and thetoolsscan for the active shell.Scope and approval
No triaged issue or maintainer-approved discussion exists for this; it is a focused, behaviour-preserving performance fix of one measured hot path (one problem, one PR, output identical to main as shown under Validation, no new setting, wire change or UI). Dedupe: I searched all open, merged and closed PRs by file and symbol and found none covering this function.
Validation
adapter.test.tsstreams 3,000 x 256 B chunks (past the trim point) and asserts the output is the last 256 KiB of the concatenation. It passes on base too, since behaviour is unchanged; no existing test coveredshell-output-delta.vp run check:changed: format, lint, knip, typecheck, tests pass.streamedOutput/shell-output-delta); perf(cursor): coalesce streamed reply text into 50 ms snapshots #24663 only coalesces text deltas.Not checked: Windows/Linux, live Cursor sessions.
Authored with Claude Sonnet 5.5 via the Claude Code harness in T3 Code.
🤖 Generated with Claude Code