Skip to content

fix(windows): resolve the Job Object native helper outside the daemon bootstrap - #2

Merged
Patodo merged 4 commits into
mainfrom
fix/windows-native-helper-resolution
Sep 17, 2026
Merged

Patodo merged 4 commits into
mainfrom
fix/windows-native-helper-resolution

Conversation

@Patodo

@Patodo Patodo commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

处理 #1 的第二部分,并修复了一条长期红灯的 CI 流水线。

背景:issue 里的两件事状态不同

issue 中的问题 状态
localapp server start 崩溃(native adapter --register 以 0xC0000374 退出,daemon 永不安装) 已由 0.2.2 修复(482e800,Rust 侧 PROPVARIANT double-free),本 PR 只补回归守卫
localapp server run 在 Windows 上无法按 README 直接使用、且只报 command_failed 仍未修复,本 PR 修复

崩溃部分:已在 0.2.2 修复(附复现证据)

在报告者机器上直接对真实二进制做对照实验:

二进制 --register --config <真实配置>
0.2.1(npm 上唯一的版本) 0xC0000374
0.2.2 0
0.2.4 0

另外修正 issue 里的一张表:报告者认为「config 位于 %TEMP% 就不崩」,据此推断崩溃由路径位置决定。实测同一份配置放在临时目录、或放到支持目录下的第二个文件,三次全部崩溃,位置无关。

崩溃点定位:把 APPDATA 置空后退出码变为 1 且不崩溃。由于 APPDATA 检查是注册表写入之后的下一行代码,这把崩溃锁死在 COM 快捷方式身份注册段,与 482e800 的结论一致。0.2.1 的二进制也确实在快捷方式写成功之后才崩。

本 PR 在 native-adapter.node-test.mjs 增加源码断言把该修复钉住(已验证这些断言在 482e800 之前的 main.rs 上会失败)。

Windows helper 解析(主要修复)

LOCALAPP_RELEASE_PATH 只由 daemon bootstrap 导出,而 helper 解析只认它。0.2.4 的 bundle 仍然如此(bin/localapp.mjs:29802),因此不只是 server run,openValidatedExternalUrl(打开浏览器)同样必然失败。

  • 新增 src/artifact-directory.ts:把「CLI 自身所在发行 artifact 目录」抽出为独立模块(同时修正 bundle 内 import.meta.url 的相对深度)。
  • resolveWindowsNativeExecutable():依次从 LOCALAPP_RELEASE_PATH、CLI 自身 artifact 目录解析 helper,且只在文件存在时才采用,仍然失败关闭。
  • spawnOwnedProcess 改为抛 native_adapter_unsupported;裸 Error 会被入口压成 command_failed。
  • README 补充解析顺序。

用真实 0.2.4 release artifact 做过端到端验证:解析路径与包内布局一致,并经该 helper 真实完成一次 --job-owner 的 Job Object 受控启动。

CI:Platform regression 长期红灯的根因

Platform regression 工作流的 46 次运行全部失败,可追溯到引入它的那次提交。失败项固定是 verification-isolation:

AssertionError: expected 404 to be 200
 ❯ tests/integration/verification-sessions.test.ts:283

根因不在产品代码:该工作流只构建了 packages/server-core,从未构建 packages/web。而 verification-isolation 会驱动生产应用入口,该入口从 Next.js 静态导出 packages/web/out/platform-shell/placeholder/placeholder.html 读取 Platform Shell;缺失时路由走 catch 分支返回 404,于是「缺 Shell」被报成了「验证边界失效」。

ci.yml 在跑同一个套件前会构建 packages/web,这就是同一个检查在 verify 里是绿的原因。

实测确认:本地构建 packages/web 后,verification-sessions.test.ts 从 1 failed 变为 8 passed,无需改动任何产品代码。

修复:工作流补上 web 构建;release-workflow.node-test.mjs 增加断言锁住「先构建 Platform Shell、再跑确定性套件」的顺序(已验证该断言对旧工作流会失败);benchmarks/agent-first-run/README.md 补记前置构建。

提交

commit 内容
078c9ae Windows helper 解析修复(主要改动)
7fe2a4e 刷新 native-adapter.test.ts 的基线摘要
943cbda CI:补上 Platform Shell 构建
2c741a3 本地守卫:基线摘要与新内容保持一致

验证

CI 全绿:

检查 结果
deterministic-platform-regression pass(该工作流 46 次运行以来首次通过)
verify pass(16m2s)
secrets pass

本地:tsc --noEmit 通过;node --test scripts/native-adapter.node-test.mjs 5 passed / 0 failed;process-tree.test.ts 12 passed;构建 packages/web 后 verification-sessions.test.ts 8 passed;test:public-source 通过且基线 29 条例外全部一致。

未做完整 localapp server start 验收:会在本机注册 Windows 计划任务并拉起 daemon。本机无 Rust 工具链,Rust 侧结论基于已发布二进制验证。

Windows 本地已知失败(与本次改动无关,HEAD 上同样失败)

  • pnpm test:platform-regression 的 capability-contract 与 server-core-capability-parity:core.autocrlf=true 使工作树为 CRLF,哈希与记录值不符;Linux CI 通过。
  • test:release-workflow 2 条失败(CRLF 正则匹配 release.yml、new URL(...).pathname 与 path.join 拼出 E:\E:\...)。
  • test:windows-release-script 1 条失败(同一 .pathname 路径 bug)。

后两类去掉 new URL(...).pathname、改用 fileURLToPath 即可修复,未纳入本 PR。

未包含

  • 未修改 localapp-native.exe 任何代码。
  • 未新增 npm 发布 workflow:release.yml 的守卫与 docs/npm-release.md 的「维护者手动发布」边界保持不变。

… bootstrap

`localapp server run`, `localapp dev`, and browser opening all spawn owned
process trees, which on Windows needs the packaged native helper for atomic
Job Object ownership. The helper was resolved only from LOCALAPP_RELEASE_PATH,
which just the daemon bootstrap exports, so a direct shell invocation always
failed. Resolve it from LOCALAPP_RELEASE_PATH first, then from the CLI's own
artifact directory (the parent of bin/localapp.mjs), and accept only a
candidate that exists. When neither yields a helper, fail closed with the
structured native_adapter_unsupported code; a bare Error was flattened to
command_failed by the CLI entrypoint, hiding the cause.

Reported symptom was `localapp server start` aborting with
native_adapter_failed. Reproduced against the installed 0.2.1 helper: it wrote
HKCU\Software\Classes\localapp and the Start Menu shortcut, then exited
0xC0000374. Clearing APPDATA returns exit 1 instead, which isolates the crash
to the COM shortcut-identity step after the registry write, and 0.2.2 already
fixed it (the PROPVARIANT drop freed a Rust Vec through CoTaskMemFree). Pin
that fix so it cannot regress silently.
…tests

The public source gate pins each credential-scan exception to the reviewed
file content by sha256, so editing packages/localapp/tests/native-adapter.test.ts
invalidated its exception and failed the export gate.

The only credential-keyed assignment in that file is unchanged
(`LOCALAPP_API_KEY` in the redaction fixture) before and after the edit, so the
entry is re-reviewed with the new digest rather than narrowed or dropped.
The Platform regression workflow never built packages/web, so
verification-isolation ran against a Server whose Platform Shell static export
was missing. The app entry then answered 404, and the check reported a missing
shell as a failed verification boundary: all 46 runs of this workflow failed,
back to the commit that introduced it. ci.yml builds packages/web before running
the same suite, which is why the identical check was green there.

Confirmed by building packages/web locally: verification-sessions.test.ts goes
from 1 failed to 8 passed with no product change. The suite's build
prerequisites are now asserted so the workflow cannot silently drop them again,
and documented in the benchmark README.
The export gate pins every reviewed scan exception to the digest of the file it
pins, so touching a baselined file fails the gate until the digest is refreshed.
That cost two CI round trips on this branch because nothing reported it locally.

Add a check that every baseline exception still matches its file. It hashes
LF-normalized content because the export hashes committed content, while a
Windows checkout materializes the same file with CRLF; the normalization is a
no-op on Linux. Verified on the real baseline: all 29 exceptions match, and the
check reports the two stale digests created by editing the test that carries it.

Refresh those digests, plus the one for scripts/release-workflow.node-test.mjs
that the workflow guard commit invalidated. The flagged content is unchanged in
each file: the baseline review covers the same canonical package assertions and
the same credential fixture as before.
@Patodo
Patodo merged commit 8e44e79 into main Sep 17, 2026
3 checks passed
Patodo added a commit that referenced this pull request Sep 17, 2026
Windows helper resolution fix (#2) plus the Platform regression workflow fix:
`localapp server run` and browser opening now resolve the packaged Job Object
helper from the CLI's own artifact, and the deterministic suite builds the
Platform Shell before it runs.

Version-bound test assertions move to 0.2.5 and the reviewed public source
baseline digests are re-recorded for the two files this touches. The flagged
content in both files is unchanged: the same three canonical package markers
and the same apiKey fixture as before.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant