microsoft / microsoft/vscode

Webview resources stream corrupted (every 256 KiB chunk delivered twice) when a second-instance CLI folder open lands during startup; corrupted response is then cached

Open
#330,243 1 comment 0 reactions 1 assignee Claimed by @deepak1556 View on GitHub
Dominant language
TypeScript
Stars
193k
Forks
42.4k
PR merge metrics
PR metrics pending

Description

## Summary

Since VS Code 1.123.0 (Chromium 148), the following sequence corrupts every resource that webviews in the affected window load through `https://file+.vscode-resource.vscode-cdn.net/...`:

1. Launch VS Code with a fresh `--user-data-dir`.
2. ~1 second later — while the first instance is still starting up — invoke the VS Code CLI from a second process to open a folder in the running instance:
`ELECTRON_RUN_AS_NODE=1 code resources/app/out/cli.js -r --user-data-dir=`
3. Open a webview (a custom editor in our case) that loads a large script via ``.

The response body arrives as `chunk0 chunk0 chunk1 chunk1 ...` at the 256 KiB (262,144-byte) chunk size — up to exactly 2.0× the on-disk size. A 10,688,919-byte script arrives as either 21,377,838 bytes (every chunk doubled) or 21,174,679 bytes (= file + exactly 40 × 262,144: the first 40 chunks doubled, the rest single — the duplication races with startup). The script then fails to parse (`SyntaxError`) and the webview renders blank.

**The corruption is then persisted:** the webview service worker caches the doubled response in the profile's CacheStorage (`WebStorage/<partition>/CacheStorage`). Because VS Code persists the webview origin per (viewType, extension) in `state.vscdb` (`memento/mainThreadCustomEditors.origins`), every later session reuses the same storage partition and **replays the corrupted cache entry even when the race does not re-occur**. One bad startup permanently poisons the profile for that webview.

## Reproduction

Runnable repro repository: **https://github.com/lordrip/vscode-webview-corruption-repro**

```bash
git clone https://github.com/lordrip/vscode-webview-corruption-repro
cd vscode-webview-corruption-repro
./reproduce.sh # downloads VS Code 1.132.0 — corrupts (~3 of 4 runs)
./reproduce.sh 1.122.0 # control — clean
```

It is a ~40-line extension (custom editor whose webview loads a generated ~9.5 MB `big.js` via `<script src>`, `fetch()`es the same URI and reports `arrayBuffer().byteLength`) plus a shell script that performs the launch + second-instance open. No test framework, no chromedriver, no other extensions.

Two details that matter:

- Workspace trust must be disabled (the script writes `{"security.workspace.trust.enabled": false}` into the fresh profile); otherwise the folder opened in step 2 starts in Restricted Mode and the repro extension never activates — masking the bug.
- It is a race: the second-instance open has to land inside a startup window of a few seconds. Delaying it (~8 s on our machine) makes the load clean.

## Smoking gun in the main-process log

During a corrupting run the main process logs every webview resource request id being answered multiple times:

```
[UtilityProcessWorker]: createWorker() found an existing worker that will be terminated (window: 1, moduleId: vs/platform/files/node/watcher/watcherMain)
[UtilityProcess id: 1, type: extensionHost, pid: <none>]: Cannot start utility process because it is already running...
RequestStore#acceptReply was called without receiving a matching request 1
RequestStore#acceptReply was called without receiving a matching request 1
RequestStore#acceptReply was called without receiving a matching request 1
RequestStore#acceptReply was called without receiving a matching request 2
... (three "acceptReply without matching request" lines for every request id)
```

`RequestStore` is the webview `loadResource` reply store — the second (CLI-triggered) open of window 1 during startup appears to register a duplicate reply channel, so every streamed resource chunk is delivered to the service worker more than once for the lifetime of the window.

## Corruption shape (measured from inside the webview)

```
byteLength: 20880780 (= exactly 2 x 10440390 for the original 10.4 MB bundle)
nullBytes: 0
headRepeatedAtIndex: 262144 (= 256 KiB)
bytes[0..256K] == bytes[256K..512K] identical
bytes[512K..768K] == bytes[768K..1024K] identical (every chunk equals its successor)
```

Not size-dependent: all 23 resources the real-world webview loads double, including a 1,066-byte file (arrives as 2,132 bytes — whole body twice).

## Cache poisoning detail

After one corrupt run, `WebStorage/1/CacheStorage/<uuid>/` contains the response entry with the doubled body. Deleting **either** the `WebStorage` dir **or** `User/globalStorage/state.vscdb` (which maps the viewType to the persisted webview origin whose QuotaManager bucket is that partition) stops the replay; keeping both replays the corrupt bytes in every later session with no second instance involved.

## Version matrix (measured, same script, same machine)

| VS Code | Chromium | Result |
| --- | --- | --- |
| 1.132.0 | 148 | corrupt 6/7 runs (ratios 2.0, 1.994, 1.981) |
| 1.122.0 | 142 | clean 3/3 |

The original investigation (KaotoIO/vscode-kaoto#1468) additionally measured 1.120.0/1.122.0 clean and 1.123.0, 1.125.0, 1.128.0, 1.130.0, 1.132.0 corrupt.

OS: Fedora Linux (kernel 7.1.5), under `xvfb-run -a`.

## Impact

`vscode-extension-tester` (Selenium-based UI testing used by many extensions) performs exactly this sequence on every run that passes `--open_resource`: VS Code is launched via chromedriver and the workspace is opened via a second-instance CLI call moments later. On CI this races — fast boots escape, slow boots corrupt — and a retried job inherits the poisoned profile, so retries fail deterministically. Every extension with a webview is effectively untestable on VS Code ≥ 1.123 (for Kaoto: KaotoIO/vscode-kaoto#1468). Pinning to 1.122.0 is the only workaround found.

---

_This bug was isolated and the reproduction built with the help of [Claude Code](https://claude.com/claude-code); all measurements were verified on real VS Code builds._

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.