Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md
2026-08-19-win32-dialog-worker-source-launch.md: 44eb6634d44c7338bae7ac90c5f50aecfa142bd2
2026-08-19-win32-dialog-worker-source-launch.zh.md: 9055289fa40a914f6506064b63388520352cf29d
2026-08-19-win32-dialog-worker-source-launch.md: 549f99a7b6c71e634d7a1d7e589a6482e8f9acac
2026-08-19-win32-dialog-worker-source-launch.zh.md: 1a59a397a015d3eba2f5b3c97f794c86bcbd5fd9
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ English | [中文](2026-08-19-win32-dialog-worker-source-launch.zh.md)

## Problem

On Windows, the source-plane folder dialog worker never started: the Web UI reported `win32 folder dialog worker exited before reporting a result`. The failure was in the launch vector, not koffi: the source arm ran `node --import tsx/esm <absolute .ts path>`. With a loader registered through `--import`, an absolute path such as `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` can be read as an `e:` scheme URL and rejected with `ERR_UNSUPPORTED_ESM_URL_SCHEME`, before the worker posts its first IPC message. The registered entry is what differs from this repository's other absolute-path launches: they register the full `tsx` entry (`packages/test-support/loader-smoke/src/index.ts`) and pass on Windows CI, while this arm registered the ESM-only `tsx/esm` hook.
On Windows, the source-plane folder dialog worker never started: the Web UI reported `win32 folder dialog worker exited before reporting a result`, and the worker died before koffi ever loaded. The source arm ran `node --import tsx/esm <absolute .ts path>`, and the reported diagnosis was that a loader registered through `--import` makes an absolute path such as `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` resolve as an `e:` scheme URL, rejected with `ERR_UNSUPPORTED_ESM_URL_SCHEME` before the first IPC message. Replacing that launch fixed the dialog on Windows.

The vector alone does not explain the failure. `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` spawns `node --import tsx/esm <absolute path>` on win32 only, and `vitest.config.ts` treats that suite as the Windows signal that lets `runner.ts` leave the per-file coverage gate; `packages/test-support/loader-smoke/src/index.ts` launches an absolute path behind a resolved tsx loader from a spec Windows does not exclude. The factor unique to this call site is `ELECTRON_RUN_AS_NODE=1` over a `process.execPath` that a packaged host points at its own binary; whether that is what turned the path into an `e:` scheme URL is untested. The decision below does not rest on settling it.

A raw `import.meta.url.endsWith('.ts')` check also decided which arm to launch. Vitest and Vite may decorate a module URL with a query string, and a decorated URL fails that suffix test, so a source-plane test could exercise the built arm — a bundler-specific test hazard rather than a cause of the Windows failure.

Expand Down Expand Up @@ -47,7 +49,7 @@ An inherited `--import` is preserved like any other entry, so a host that regist

The `dsh` CLI source launch keeps the tsx ESM hook because its graph needs a transform mode Node no longer ships, per [the source-launch decision](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md); that constraint is about the CLI graph, not about native stripping being unavailable in the engines range.

`packages/sandbox/sandbox-local/src/index.ts` still builds this vector — the same ESM-only `tsx/esm` hook in front of an absolute path — for the windows-acl runner's source arm, and that graph is package-local and erasable too, so the same launch applies there. It is a separate change: it also rewrites the assertion in `packages/sandbox/sandbox-local/tests/local.spec.ts` that pins the `--import tsx/esm` prefix.
`packages/sandbox/sandbox-local/src/index.ts` builds the same argv for the windows-acl runner's source arm, and `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` runs exactly that argv on win32, which is why it is not recorded here as a second instance of this failure. Its graph is package-local and erasable, so this launch would suit it as a simplification — one that rewrites the `--import tsx/esm` assertion in `packages/sandbox/sandbox-local/tests/local.spec.ts` — not as a fix.

`packages/workflow/workflow-worker-thread/src/host.ts` selects its own source/built arm from the raw `import.meta.url`, but it boots the worker from a `data:` URL carrying a proper `file://` href, so the `e:` scheme failure cannot reach that launch. Its raw check does leave the query-string hazard: on a built tree a decorated URL selects `worker.cjs`, so a source-plane test there can exercise built code — which artifact a test covers, not a production launch.

Expand All @@ -61,7 +63,7 @@ The `dsh` CLI source launch keeps the tsx ESM hook because its graph needs a tra

## Consequences

- Windows source launches (`pnpm dsh web`) run the worker directly under Node's native type stripping, so no loader chain can read the worker path as an `e:` scheme URL.
- Windows source launches (`pnpm dsh web`) run the worker directly under Node's native type stripping, so the launch no longer depends on how a loader chain resolves the worker path.
- Packaged hosts keep the unchanged CJS worker arm and an untouched `NODE_OPTIONS`.
- The source arm depends on the engines range, a package-local erasable-only graph, marked type imports, and removal of inherited type-stripping disable flags; [the package README](../../../../packages/host/directory-picker-native/README.md) states those preconditions for consumers.
- The Win32 smoke reaches the real source launch even where a module runner decorates URLs with query strings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Status: implemented

## 问题

Windows 上源码面的文件夹对话框 worker 从未启动成功:Web UI 只报出 `win32 folder dialog worker exited before reporting a result`。故障出在启动向量而非 koffi:源码分支运行的是 `node --import tsx/esm <绝对路径 .ts>`。通过 `--import` 注册 loader 后,像 `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` 这样的绝对路径可能被读作 `e:` scheme URL 并以 `ERR_UNSUPPORTED_ESM_URL_SCHEME` 拒绝,此时 worker 还没发出第一条 IPC 消息。与仓库中其他「绝对路径」启动的区别在于注册的入口:它们注册的是完整的 `tsx` 入口(`packages/test-support/loader-smoke/src/index.ts`),在 Windows CI 上是绿的,而这个分支注册的是仅 ESM 的 `tsx/esm` hook。
Windows 上源码面的文件夹对话框 worker 从未启动成功:Web UI 只报出 `win32 folder dialog worker exited before reporting a result`,且 worker 在 koffi 加载之前就已死亡。源码分支运行的是 `node --import tsx/esm <绝对路径 .ts>`,当时的诊断是:通过 `--import` 注册 loader 后,像 `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` 这样的绝对路径会被解析成 `e:` scheme URL,并在第一条 IPC 消息之前以 `ERR_UNSUPPORTED_ESM_URL_SCHEME` 被拒绝。换掉这个启动方式确实让 Windows 上的对话框恢复了。

但仅凭启动向量无法解释这次故障。`packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` 只在 win32 上以 `node --import tsx/esm <绝对路径>` 启动,而 `vitest.config.ts` 正是把这套测试当作让 `runner.ts` 免于每文件覆盖率门禁的 Windows 信号;`packages/test-support/loader-smoke/src/index.ts` 也在 Windows 未排除的 spec 中以解析后的 tsx loader 启动绝对路径。本调用点独有的因素是 `ELECTRON_RUN_AS_NODE=1` 叠加打包宿主指向自身二进制的 `process.execPath`;它是否就是把路径变成 `e:` scheme URL 的原因,尚未验证。下面的决策并不依赖于把这一点定论。

决定启动哪个分支的判断此前读的是裸 `import.meta.url`。Vitest 与 Vite 可能给模块 URL 附加查询串,带查询串的 URL 通不过这个后缀判断,于是源码面的测试可能跑到 built 分支上——这属于 bundler 测试环境的风险,而不是 Windows 故障的成因。

Expand Down Expand Up @@ -47,7 +49,7 @@ spawn(process.execPath, [fileURLToPath(new URL('./win32-dialog-worker.ts', impor

`dsh` CLI 的源码启动保留 tsx ESM hook,因为它的源码图需要 Node 已不再提供的 transform 模式,见[源码启动决策](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md);那条约束针对的是 CLI 源码图,而不是说 engines 范围内没有原生剥离。

`packages/sandbox/sandbox-local/src/index.ts` 仍在为 windows-acl runner 的源码分支拼出同一个启动向量——同样是仅 ESM 的 `tsx/esm` hook 加绝对路径,而那个源码图同样包内闭合且可擦除,因此同样的启动方式适用。它属于独立改动:一并要改写 `packages/sandbox/sandbox-local/tests/local.spec.ts` 中钉住 `--import tsx/esm` 前缀的断言
`packages/sandbox/sandbox-local/src/index.ts` windows-acl runner 的源码分支拼出同样的 argv,而 `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` 正是在 win32 上跑这套 argv,因此这里不把它记作本故障的第二例。它的源码图同样包内闭合且可擦除,所以这套启动方式适合它——作为一次简化,并需一并改写 `packages/sandbox/sandbox-local/tests/local.spec.ts` 中的 `--import tsx/esm` 断言——而不是作为修复

`packages/workflow/workflow-worker-thread/src/host.ts` 同样从裸 `import.meta.url` 选择源码/构建分支,但它的 worker 从携带正确 `file://` href 的 `data:` URL 启动,`e:` scheme 故障触及不到那条启动路径。它的裸判断确实留下了查询串风险:在已构建的树上,带查询串的 URL 会选中 `worker.cjs`,于是那里的源码面测试可能跑到构建产物上——这影响测试覆盖的是哪个产物,而非生产启动。

Expand All @@ -61,7 +63,7 @@ spawn(process.execPath, [fileURLToPath(new URL('./win32-dialog-worker.ts', impor

## 后果

- Windows 源码启动(`pnpm dsh web`)直接由 Node 原生类型剥离运行 worker,不再有任何 loader 链会把 worker 路径读成 `e:` scheme URL
- Windows 源码启动(`pnpm dsh web`)直接由 Node 原生类型剥离运行 worker,启动不再取决于 loader 链如何解析 worker 路径
- 打包宿主保持不变的 CJS worker 分支,`NODE_OPTIONS` 不被改写。
- 源码分支依赖 engines 范围、包内闭合且只含可擦除语法的依赖图、标注过的类型导入,以及移除继承的类型剥离禁用 flag;[包 README](../../../../packages/host/directory-picker-native/README.md) 为使用者写明了这些前提。
- 即使模块运行器给 URL 附加查询串,Win32 冒烟测试也能进入真实的源码启动。
Expand Down
Loading