Skip to content

fix(shell): name a DLL-initialisation exit as EXECUTOR_UNAVAILABLE - #563

Closed
tietoumiao wants to merge 1 commit into
T-Auto:mainfrom
tietoumiao:fix/417-shell-exit-code-name
Closed

tietoumiao wants to merge 1 commit into
T-Auto:mainfrom
tietoumiao:fix/417-shell-exit-code-name

Conversation

@tietoumiao

@tietoumiao tietoumiao commented Nov 1, 2026 •

Copy link
Copy Markdown

Closes #417

What was wrong

On Windows, ops_bash can die before any command runs and report exit code 3221225794
(0xC0000142, STATUS_DLL_INIT_FAILED) with completely empty stdout/stderr. Nothing in src/
mapped that code, so src/shells/failure.ts classified it by stderr text and produced EXIT_CODE
with the advice "correct the command or its inputs before retrying" — advice that cannot work,
because no command was ever executed. The caller then edits a command that was never the problem,
or retries the same failing shell.

Minimal repro: a shell executor that exits with 3221225794 and writes nothing to either stream.
Observed on Windows 11 x64 against dsh-ops 0.5.0-alpha3 (main 065cd7f); the same family is
described in #409/#480 (their sandbox root cause is out of scope here).

The change

  • src/shells/failure.ts — recognise the loader-failure exit codes (unsigned 3221225794 and
    signed -1073741502), settle them as EXECUTOR_UNAVAILABLE with retry: never, and say what
    actually happened ("the process died during DLL initialisation; no command ran, so editing the
    command cannot change it — repair the shell runtime or the DSH sandbox"). Every more specific
    class (error / restriction / permission / signal / timeout / cancel) still wins, and the raw exit
    code stays on the result.
  • test/shells-host.test.mjs — a case driven by fixture({ exitCode: 3221225794 }) plus the
    signed spelling through hostForeground({ exitCode: -1073741502 }).
  • docs/troubleshooting.md + docs/常见问题.md + docs/CHANGELOG.md — bilingual entry and the new
    failure class.

Deliberately not done: no change to native/, to projectResult's sandbox handling, or to the
restricted-token root cause of #409/#480; the message-collapsing behaviour of #512 is untouched.

Validation

$ node test/run.mjs shells-host
  ok   a child that dies in DLL initialisation is named, not reported as a command exit
dsh-ops shells-host: all 24 checks passed
dsh-ops test: 1 suite(s) passed in 0s

$ node test/run.mjs shells-host          # after reverting only src/shells/failure.ts
  FAIL a child that dies in DLL initialisation is named, not reported as a command exit
       + 'EXIT_CODE'
       - 'EXECUTOR_UNAVAILABLE'
       at test/shells-host.test.mjs:265:10
dsh-ops shells-host: 1 of 24 check(s) failed
  • 撤掉本次实现后变红的检查:shells-host 的 a child that dies in DLL initialisation is named…
  • docs/CHANGELOG.md ## [Unreleased]:已加(EN+ZH 成对)
  • 面向模型的描述 / 参数:无变化(不涉及 tool schema 与提示词字节)
  • CI:待维护者批准后由本仓库 pull_request 事件触发;fork 首跑可能停在 action_required,
    届时回填 CI required 的 run URL(本机无法生成该 run)

Environment

  • OS / 架构:Windows 11 x64(10.0.26300)
  • Node:24.16.0
  • Rust:1.97.1(本片未改 Rust)
  • 基于上游 main 的 commit:065cd7f
  • 安装通道:本仓库源码 tarball + npm ci --ignore-scripts,cargo build --release --locked 自行 stage worker
  • 全量套件:本机 node test/run.mjs = 4 of 167 failed,与 pristine main 同口径的失败集合一致
    (bash-ipc、bash-ipc-roots、linux-paths、permissions,均为本机受限 bash 抖动,见正文)

未验证 / 风险披露

  • 本机无法复现真实的 0xC0000142 现场(受限 MSYS 场景),断言直接喂两个拼写的退出码。
  • 后台/job 路径复用 hostForeground→bashFailure,但没有套件真的驱动一个 job 以该码退出。
  • 退出码被有意保留在结果里;若确有命令以该码正常退出,会被描述成加载失败(已知取舍)。

CI

$ gh run view 41338348125 --json url,event,conclusion --jq '"(.url) (.event) (.conclusion)"'
https://github.com/T-Auto/dsh-ops/actions/runs/41338348125 pull_request action_required

Exit code 3221225794 (0xC0000142 STATUS_DLL_INIT_FAILED, signed
-1073741502) means the child died while loading its own DLLs: the command
text never ran and both output streams stay empty. Classifying it as an
ordinary EXIT_CODE told the caller to correct a command that never
executed and to retry, which cannot help (T-Auto#417).

Treat a bare loader exit code as EXECUTOR_UNAVAILABLE with retry never
and advice that names the condition, for both spellings of the NTSTATUS.
Keep every more specific classification (error, restriction, permission,
signal, timeout, cancellation) ahead of it.

Assert it in the shell-host suite for the unsigned fixture exit code and
the signed host value, document the symptom in both troubleshooting
guides, and record the change in the changelog.

@Haoming9527 Haoming9527 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The diagnostic goal is useful, and the local build plus all 24 shells-host checks pass at 1afbf1f. There is one correctness issue that should block merging this version.

[P1] A numeric exit status cannot establish that no command ran (src/shells/failure.ts, the new message and nextAction branches). I reproduced this with an actual Windows Node child that writes a marker and then exits with 0xC0000142:

require('node:fs').writeFileSync(process.argv[1], 'command definitely ran')
process.exit(0xC0000142)

The parent observes exit code 3221225794, empty stdout/stderr, and the marker on disk. Passing that result through this PR's bashFailure(value, 'unknown') produces both effectState: 'unknown' and “no command ran.” A second control with nonempty stdout/stderr produces the same incorrect claim. Thus adding an empty-output check would not establish absence of effects. The existing fixture also returns stdout: 'ok' / stderr: 'warning', so the new test does not represent the empty-stream case described in the body.

Please keep the useful status name and no-blind-retry guidance, but preserve uncertainty about initialization stage, command execution, and prior effects. The troubleshooting paragraphs need the same correction; this status alone also does not prove a restricted-token cause or that reinstalling the payload is the remedy. A regression should execute a child that writes a marker before returning this code and verify the diagnostic tells the caller to inspect prior effects.

Related work: #543 already handles both signed/unsigned spellings with that conservative diagnostic and is included in integration PR #557. Its comparison result for the same real child retains effectState: 'unknown', names STATUS_DLL_INIT_FAILED, and says to inspect prior effects without rerunning. These overlapping implementations should not both be applied; please coordinate any revision against #543/#557. This review leaves your PR open for revision and does not change the reported host-runtime root causes in #409/#480.

CI run 41338348125 is still awaiting approval; local checks above are not a replacement for repository CI.

@Nicholas023 Nicholas023 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查结论:REQUEST_CHANGES — 建议关闭

我独立在本机复现了核心问题,其中包括一条会直接误导调用方的错误断言。

1. 「no command ran」是错误的、可被证伪的断言(阻断)

用真实 Windows 子进程:先写 marker 文件,再 process.exit(0xC0000142):

js require('node:fs').writeFileSync(marker, 'command definitely ran') process.exit(0xC0000142)

父进程观察到 exitCode: 3221225794、stdout/stderr 均为 0 字节、marker 在磁盘上。把这个结果喂给本 PR 的 bashFailure():

json { "code": "EXECUTOR_UNAVAILABLE", "message": "… so no command ran.", "effectState": "unknown", "retry": "never", "nextAction": "… no command ran, so editing the command or its arguments cannot change it …" }

同一个 payload 一边说副作用未知,一边说没有命令运行。dllInitExitCode() 只看 exit code,完全不看 stdout/stderr,所以「加空流检查」也补救不了:命令可以在产生输出之前就完成副作用。

2. 新测试没有覆盖它声称的场景

fixture 固定返回 stdout:'ok' / stderr:'warning',而 PR body 描述的是「空输出」场景。

3. 与 #543 重复且补丁级冲突

两 PR 改同一 hunk;git apply --check 实测 #543 的补丁打在 #563 树上 exit 1。

4. 缺少 win32 门禁

POSIX 上 bash 退出码是 8-bit,该 NTSTATUS 分支不可达,属死分支。

已核实为真的部分

  • 0.5.0 基线确实把该码报成 EXIT_CODE + 「correct the command」(问题本身真实存在)
  • node test/run.mjs shells-host → 24/24 passed
  • revert-red(只撤 src/shells/failure.ts)→ 1/24 failed,与 PR 声明一致

处置建议

关闭。 由 #543 承接(它保留了 effectState:'unknown',要求先检查既有副作用、不宣称命令未运行)。若要保留 EXECUTOR_UNAVAILABLE 这个码名,那是独立的 taxonomy 决策,不能沿用本 PR 的错误文案。

@T-Auto T-Auto closed this Nov 2, 2026
@tietoumiao

Copy link
Copy Markdown
Author

Closing as advised — #543 is the single taxonomy source (STATUS_DLL_INIT_FAILED with the hedged claim), and it is already in main (#557). The falsifiable point in the review stands: a bare exit code cannot prove that no command ran, so the EXECUTOR_UNAVAILABLE wording should not survive without an effect check. Nothing from this branch is needed on top of #543.

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.

ops_bash dies with 0xC0000142 under workspace-write on Windows 10 (root cause in DSH sandbox)

4 participants