You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Does this issue occur when all extensions are disabled?: Yes
VS Code Version
Reproduced against code-server 4.106.x (vendoring vscode1.107.x). Bug exists in current microsoft/vscode@main per source inspection of src/vs/base/common/buffer.ts and src/vs/base/parts/ipc/common/ipc.net.ts. Per the introducing commit (#76076) it has been present since 2019.
OS Version
Server: Ubuntu 24.04, Node 20.x
Clients (reproduces): Safari 18 on macOS 15, Safari 18 on iPadOS 18, Chrome / Brave / Firefox on iOS (all forced WebKit by App Store policy)
Clients (does not reproduce): Chrome / Chromium / Brave / Edge on macOS / Linux / Windows (V8), Firefox desktop (SpiderMonkey)
Does this reproduce in native VS Code desktop?
No. Native VS Code (Electron) uses Electron's IPC layer between renderer and extension host — not browser postMessage with transfer lists for VSBuffer payloads. The underlying ArrayBuffer is never detached in that flow, so ChunkStream's held subarray views stay valid and VSBuffer.slice's view-returning behavior is correct. The bug is specifically a browser-mode VS Code issue.
Does this reproduce in code serve-web?
Yes (architecturally identical, not directly stood up).code serve-web ships the same VSBuffer.slice code path and the same browser-based web-worker extension host. Any Safari / iOS client of code serve-web should hit it identically. Reproduces deterministically in code-server, which vendors the same upstream code verbatim.
Does this reproduce in vscode.dev?
Likely yes. I have not directly tested in Safari since vscode.dev is harder to drive a webview-heavy extension on (the marketplace surface is narrower). Long-standing issue #213143 ("Safari 14 no longer able to load vscode.dev") and adjacent reports point in the same direction. Same upstream code path.
Does this reproduce in GitHub Codespaces?
Likely yes (Codespaces uses VS Code web). Not directly tested.
Steps to Reproduce
Run any browser-mode VS Code (vscode.dev, code serve-web, code-server, Codespaces) over HTTPS — service workers must register.
Activate any extension that delivers initial webview content as multi-chunk binary IPC frames. Reliable repro: Claude Code (Anthropic.claude-code version 2.1.77 specifically; newer versions hit an unrelated Anthropic-side regression that masks this bug). Alternative repros that hit the same code path: vscode-pdf (open a PDF), .ipynb notebook renderers (open a notebook with rendered output), Live Preview.
Open in Safari (macOS or iPadOS) or any iOS browser. Open Web Inspector → Console.
Click the extension's activity-bar icon to open its webview panel.
Panel stays permanently blank. Console fires the cascade below.
Confirm it does NOT reproduce on V8: open the same URL in Chrome / Edge on macOS / Linux / Windows. Panel renders normally.
Errors from the Dev Tools Console
[Log] [Extension Host] Extension activated!
[Error] TypeError: Underlying ArrayBuffer has been detached from the view or out-of-bounds
Uint8Array.prototype.slice
VSBuffer.prototype.slice (out/vs/base/common/buffer.js)
ChunkStream._read (out/vs/base/parts/ipc/common/ipc.net.js)
ProtocolReader.acceptChunk (out/vs/base/parts/ipc/common/ipc.net.js)
…
…fires once per IPC chunk on the management socket, then again on the extension-host socket. The cascade kills the channel; any subsequent IPC traffic for the extension is dead until reload.
Root cause
VSBuffer.prototype.slice (src/vs/base/common/buffer.ts) currently returns:
This is a view into the parent ArrayBuffer, not a copy — a deliberate perf optimization from #76076. The new VSBuffer's .buffer.buffer === this.buffer.buffer.
In vs/base/parts/ipc/common/ipc.net.ts, ChunkStream queues incoming VSBuffer chunks and uses .slice() to extract messages:
Both result and the residual chunk are views over the same ArrayBuffer that arrived in the WebSocket binary frame.
Downstream, the extracted message is delivered to the web-worker extension host iframe via postMessage(msg, [transferList]). The transfer list detaches the underlying ArrayBuffer in the originating window.
ChunkStream still holds chunks whose .buffer is now a view into a detached ArrayBuffer. The next read calls Uint8Array.prototype.slice on that view.
V8 (Chromium / Node / Electron) is permissive about this. Historically TypedArray.prototype.slice on a view of a detached buffer returned an empty buffer; modern V8 throws, but the timing of the detach vs. the read is loose enough that the bug rarely fires in practice. This is why the bug is essentially invisible on every desktop browser and on Electron.
JavaScriptCore (Safari, iOS browsers) throws synchronously and unconditionally per TC39 ECMA-262 §25.1.2.1 (IsDetachedBuffer). The IPC reader unwinds, handleUnexpectedError fires, the channel is dead.
The detach-then-read race only fires for messages that need to be sliced (multi-chunk read) AND get transferred immediately downstream (webview content delivery). Routine editor traffic is single-chunk and short-lived, so it never hits the race. Webview-heavy extensions hit it on their very first frame.
Candidate fixes (ranked by blast radius)
A. Make VSBuffer.slice copy unconditionally (simplest, broadest)
public slice(start?: number, end?: number): VSBuffer {
- return new VSBuffer(this.buffer.subarray(start, end));+ return new VSBuffer(this.buffer.slice(start, end));
}
Cons: walks back the perf optimization in all environments, including desktop Electron where the bug is invisible. Cost: one memcpy per VSBuffer.slice call. Sub-kilobyte typical IPC message sizes; in benchmarks against a sustained extension-host workload (Claude Code session, multiple notebook renderers) I haven't been able to measure the regression above noise, but I'm flagging it.
I'd open this as a Draft RFC PR alongside this issue. If maintainers prefer B or C below, easy to rewrite.
B. Make VSBuffer.slice copy only in web/browser bundles (perf-preserving)
Use the existing browser/common/node source layout to split implementations:
(Or via a build-target conditional that swaps the implementation file.)
Pros: zero perf impact on desktop Electron where the bug is invisible; only the affected platform pays the copy cost.
Cons: introduces platform-conditional semantics for a primitive that should arguably behave the same everywhere. Asymmetric semantics is a footgun for future contributors.
C. Copy at the IPC reader, not in VSBuffer.slice (most surgical)
Have ChunkStream._read produce owned buffers regardless of slice semantics:
Pros: minimum semantic blast radius; the rest of the codebase keeps the view-returning slice for everywhere else.
Cons: more code; doesn't fix the same class of bug if it surfaces in any other code path that holds a view across a transferable boundary (and there are several such paths — anywhere a VSBuffer is held while another caller might post the original).
I believe (C) is partial: it cures the symptom in the IPC reader specifically, but the underlying contract violation (slice returns a view that can outlive its source's lifecycle) remains lurking elsewhere. Fine if maintainers want the minimum change; (A) is the more durable fix.
Verifying the fix
A regression test that fails before (A) and passes after, suitable for src/vs/base/test/common/buffer.test.ts:
test('VSBuffer#slice returns an independent buffer that survives detachment of the source',()=>{// VSBuffer instances are typically views into a larger ArrayBuffer// carrying IPC payload. Once a slice is extracted, downstream code may// transfer the original buffer to another execution context (e.g. an// extension-host iframe via postMessage). On WebKit (JavaScriptCore),// accessing any view into a detached ArrayBuffer throws synchronously// per ECMA-262 §25.1.2.1.//// VSBuffer#slice must therefore return a buffer that owns its data, not// a view into the source. This test exercises that contract by detaching// the source after slicing and asserting the slice is still readable.constsource=VSBuffer.alloc(1024);source.buffer[0]=42;constslice=source.slice(0,512);// Detach the source's underlying ArrayBuffer.source.buffer.buffer.transfer();assert.strictEqual(source.buffer.byteLength,0,'source.buffer should be detached');// The slice must still be readable — it owns its own storage.assert.strictEqual(slice.byteLength,512);assert.strictEqual(slice.buffer[0],42);});
ArrayBuffer.prototype.transfer() is in V8 since Chrome 114 (May 2023) and Node 21.7. If the runtime VS Code targets for unit tests is older, the test can use structuredClone(buf, { transfer: [buf] }) or MessageChannel.postMessage(_, [buf]) instead, with the same semantic effect.
Production validation
We've been running fix (A) — applied as a one-character sed to the bundled workbench.js post-install — in production across three independent code-server deployments for ~24h. Before the patch, every webview-heavy extension session on Safari/iPad fired the cascade and stayed blank. After the patch, all WebKit clients work normally with no observable side effects or perf regressions. The same patch on V8 clients is a no-op (Chromium was already working).
Patch site in the minified bundle (offset confirmed at column ~97282 of line ~406 in out/vs/code/browser/workbench/workbench.js for code-server's vendored 1.107.x build):
-slice(i,e){return new a(this.buffer.subarray(i,e))}+slice(i,e){return new a(this.buffer.slice(i,e))}
If maintainers want a self-contained Docker Compose reproducer (code-server + minimal extension + the broken-on-WebKit state vs. the patched-and-working state, with HAR captures), I'm happy to set one up.
Why this matters
Every Safari and iOS user of browser-based VS Code is currently locked out of every webview-heavy extension. That includes:
vscode.dev users on Safari (long surface, hard to deterministically repro outside of a binary-protocol extension).
All code-server deployments — disproportionately the population that hits this, because code-server is browser-only and the iPad/iOS audience is large among self-hosted dev environments.
code serve-web and Codespaces users on Safari/iOS.
Adjacent issues over the past several years that all roll up to the same root cause but were filed and closed without the underlying mechanism being identified:
Does this issue occur when all extensions are disabled?: Yes
VS Code Version
Reproduced against code-server
4.106.x(vendoringvscode1.107.x). Bug exists in currentmicrosoft/vscode@mainper source inspection ofsrc/vs/base/common/buffer.tsandsrc/vs/base/parts/ipc/common/ipc.net.ts. Per the introducing commit (#76076) it has been present since 2019.OS Version
Does this reproduce in native VS Code desktop?
No. Native VS Code (Electron) uses Electron's IPC layer between renderer and extension host — not browser
postMessagewith transfer lists forVSBufferpayloads. The underlyingArrayBufferis never detached in that flow, soChunkStream's held subarray views stay valid andVSBuffer.slice's view-returning behavior is correct. The bug is specifically a browser-mode VS Code issue.Does this reproduce in
code serve-web?Yes (architecturally identical, not directly stood up).
code serve-webships the sameVSBuffer.slicecode path and the same browser-based web-worker extension host. Any Safari / iOS client ofcode serve-webshould hit it identically. Reproduces deterministically incode-server, which vendors the same upstream code verbatim.Does this reproduce in
vscode.dev?Likely yes. I have not directly tested in Safari since
vscode.devis harder to drive a webview-heavy extension on (the marketplace surface is narrower). Long-standing issue #213143 ("Safari 14 no longer able to load vscode.dev") and adjacent reports point in the same direction. Same upstream code path.Does this reproduce in GitHub Codespaces?
Likely yes (Codespaces uses VS Code web). Not directly tested.
Steps to Reproduce
code serve-web, code-server, Codespaces) over HTTPS — service workers must register.Anthropic.claude-codeversion2.1.77specifically; newer versions hit an unrelated Anthropic-side regression that masks this bug). Alternative repros that hit the same code path: vscode-pdf (open a PDF),.ipynbnotebook renderers (open a notebook with rendered output), Live Preview.Errors from the Dev Tools Console
…fires once per IPC chunk on the management socket, then again on the extension-host socket. The cascade kills the channel; any subsequent IPC traffic for the extension is dead until reload.
Root cause
VSBuffer.prototype.slice(src/vs/base/common/buffer.ts) currently returns:This is a view into the parent
ArrayBuffer, not a copy — a deliberate perf optimization from #76076. The newVSBuffer's.buffer.buffer === this.buffer.buffer.In
vs/base/parts/ipc/common/ipc.net.ts,ChunkStreamqueues incomingVSBufferchunks and uses.slice()to extract messages:Both
resultand the residual chunk are views over the sameArrayBufferthat arrived in the WebSocket binary frame.Downstream, the extracted message is delivered to the web-worker extension host iframe via
postMessage(msg, [transferList]). Thetransferlist detaches the underlyingArrayBufferin the originating window.ChunkStreamstill holds chunks whose.bufferis now a view into a detachedArrayBuffer. The next read callsUint8Array.prototype.sliceon that view.TypedArray.prototype.sliceon a view of a detached buffer returned an empty buffer; modern V8 throws, but the timing of the detach vs. the read is loose enough that the bug rarely fires in practice. This is why the bug is essentially invisible on every desktop browser and on Electron.handleUnexpectedErrorfires, the channel is dead.The detach-then-read race only fires for messages that need to be sliced (multi-chunk read) AND get transferred immediately downstream (webview content delivery). Routine editor traffic is single-chunk and short-lived, so it never hits the race. Webview-heavy extensions hit it on their very first frame.
Candidate fixes (ranked by blast radius)
A. Make
VSBuffer.slicecopy unconditionally (simplest, broadest)public slice(start?: number, end?: number): VSBuffer { - return new VSBuffer(this.buffer.subarray(start, end)); + return new VSBuffer(this.buffer.slice(start, end)); }memcpyperVSBuffer.slicecall. Sub-kilobyte typical IPC message sizes; in benchmarks against a sustained extension-host workload (Claude Code session, multiple notebook renderers) I haven't been able to measure the regression above noise, but I'm flagging it.I'd open this as a Draft RFC PR alongside this issue. If maintainers prefer B or C below, easy to rewrite.
B. Make
VSBuffer.slicecopy only in web/browser bundles (perf-preserving)Use the existing
browser/common/nodesource layout to split implementations:(Or via a build-target conditional that swaps the implementation file.)
C. Copy at the IPC reader, not in
VSBuffer.slice(most surgical)Have
ChunkStream._readproduce owned buffers regardless ofslicesemantics:slicefor everywhere else.VSBufferis held while another caller might post the original).I believe (C) is partial: it cures the symptom in the IPC reader specifically, but the underlying contract violation (
slicereturns a view that can outlive its source's lifecycle) remains lurking elsewhere. Fine if maintainers want the minimum change; (A) is the more durable fix.Verifying the fix
A regression test that fails before (A) and passes after, suitable for
src/vs/base/test/common/buffer.test.ts:ArrayBuffer.prototype.transfer()is in V8 since Chrome 114 (May 2023) and Node 21.7. If the runtime VS Code targets for unit tests is older, the test can usestructuredClone(buf, { transfer: [buf] })orMessageChannel.postMessage(_, [buf])instead, with the same semantic effect.Production validation
We've been running fix (A) — applied as a one-character
sedto the bundledworkbench.jspost-install — in production across three independent code-server deployments for ~24h. Before the patch, every webview-heavy extension session on Safari/iPad fired the cascade and stayed blank. After the patch, all WebKit clients work normally with no observable side effects or perf regressions. The same patch on V8 clients is a no-op (Chromium was already working).Patch site in the minified bundle (offset confirmed at column ~97282 of line ~406 in
out/vs/code/browser/workbench/workbench.jsfor code-server's vendored 1.107.x build):If maintainers want a self-contained Docker Compose reproducer (code-server + minimal extension + the broken-on-WebKit state vs. the patched-and-working state, with HAR captures), I'm happy to set one up.
Why this matters
Every Safari and iOS user of browser-based VS Code is currently locked out of every webview-heavy extension. That includes:
vscode.devusers on Safari (long surface, hard to deterministically repro outside of a binary-protocol extension).code-serverdeployments — disproportionately the population that hits this, because code-server is browser-only and the iPad/iOS audience is large among self-hosted dev environments.code serve-weband Codespaces users on Safari/iOS.Adjacent issues over the past several years that all roll up to the same root cause but were filed and closed without the underlying mechanism being identified:
Downstream tracking: coder/code-server#7801. Draft PR for candidate fix (A) is open: #316842.
Draft PR for candidate fix (A) is up at #316842, opened as Draft to invite (B) / (C) feedback before un-drafting.