Skip to content

[BUG] Windows workspace-write:ops_bash 静默假成功(exit 0、stdout 空、命令未执行) #373

Closed as not planned

Description

@cloveryn

Related: #363(同源 IPC 缺陷,但本例是它的静默变体:不报 fatal error、不返回非零 exit、模型无法察觉失败)

环境

  • Windows 11 build 26100, x64
  • DSH Desktop 2.0.18,DSH core 0.2.1-rc.2
  • dsh-ops 0.4.1,载荷 @dsh-ops/bash-win32-x64@0.2.1
  • msys-2.0.dll sha256 = 2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7(与 provenance.json 一致)
  • 会话沙箱:workspace-write

现象

workspace-write 会话下,任意 ops_bash 调用都返回"成功"但实际未执行:

  • 返回 exitCode: 0,stdout / stderr 均为空
  • 命令没有真正运行:
    • exit 42 返回 0
    • sleep 3 立即返回(无 3 秒延迟)
    • echo x > probe.txt 文件未创建(用宿主 PowerShell 复检确认)
  • 加 includeDiagnostics: true 也不返回任何 diagnostics 字段

关键区别:#363 报告的是 fatal error - CreateFileMapping ... Win32 error 5 + EXECUTOR_UNAVAILABLE(模型能识别失败);本例完全静默,模型会把未执行的命令当成执行成功,这是更危险的失败模式。

对照实验(同一台机器、同一会话)

# 实验 结果
1 宿主 pwsh 跑命令 正常回显 ✅
2 ops_bash 跑 echo exit 0,输出空 ⚠️
3 ops_bash 跑 exit 42 返回 0 ⚠️(应为 42)
4 ops_bash 跑 sleep 3 立即返回 0 ⚠️(无延迟,证明未执行)
5 ops_bash 指定简单 ASCII workdir 同样空输出 ⚠️(排除路径含特殊字符)
6 切换会话到 danger-full-access 完全正常 ✅
7 宿主 shell 直接调原始 bundled bash(node_modules/@dsh-ops/bash-win32-x64/bin/bash.exe) couldn't create signal pipe, Win32 error 5 ❌(符合文档描述的受限令牌限制)
8 宿主 shell 直接调打补丁的 bash-ipc 副本(~/.dsh/dsh-ops/bash-ipc/<id>/usr/bin/bash.exe) patched-ok,exit=42 正确 ✅

第 7、8 条是关键:confinedBash() 产出并校验过的运行时本身可用,手工调用完全正常。所以故障出在 sandbox.confine() → ctx.subprocess.spawn() 这条链上,而不是 IPC 补丁或载荷损坏。

已排除

  • 不是 [BUG] Windows 受限令牌下,常驻 MSYS 进程 + 另一次独立 ops_bash 必崩:opsnt.cpp 的 selected_section 未覆盖 Cygwin 每用户共享段 S-<sid>.<n> #363 的残留进程问题:Get-Process 检查无任何 bash-ipc / bash / sh / sleep 残留进程,依然稳定复现
  • 不是路径含特殊字符:workspace 路径为纯 ASCII,且指定其它 workdir 结果相同
  • 不是 bash 载荷损坏:~/.dsh/dsh-ops/bash-ipc/<id>/.ops-ipc.json 收据完整,generationIntact() 校验通过(手工执行成功即为证据)
  • 不是插件冲突:profile 内无第二个 shell 插件;dsh-ops ladder 显示 bash 唯一解析到 bundled 路径且 restricted: yes
  • 不是审批被拒:approval.js 的拒绝路径会抛 APPROVAL_* / EXECUTOR_UNAVAILABLE 错误,不会返回空成功
  • 不是沙箱拒绝:无 [sandbox: ... denied] 标记,且 denied 判定要求 exitCode !== 0

推测的故障点(供参考,未验证)

lib/shells/execution.js:89 的 subprocess.spawn() 与 lib/shells/session-execution.js:39 的 sandbox.confine() 之间。

现象看起来是 handle.done 立即 resolve 成一个空的成功 outcome,导致 settledStream() 读到空 reader、exitCode 落成 0,而 projectResult() 因为 exitCode === 0 判定"正常完成"。也就是说:spawn 实际上没有发生(或立即失败),但失败没有被传播到 value.error。

建议在此处加一个断言:handle.started === false 时不应产出 exitCode: 0 的成功结果,而应返回 EXECUTOR_UNAVAILABLE。即便是未定位的底层原因,这个 fail-closed 检查也能避免模型被假成功误导。

附带发现(独立小问题)

lib/shells/constants.js:44 的 BASH_ARGUMENTS 只有 7 个参数:

['command', 'description', 'script_path', 'workdir', 'timeoutMs', 'graceMs', 'includeDiagnostics']

工具的 parameters.properties 里没有 run_in_background。但 lib/policy.js:94 注入给模型的提示词写着:

ops_bash requires description and returns kind foreground, background or promoted. Foreground has status (completed/failed), exitCode, stdout/stderr and error; background/promoted has jobId for host job_output/job_kill.

这与 schema 不一致(模型会尝试传 run_in_background,但 schema 不接受,且 assertKnownArguments 会拒绝未知参数)。建议核对这两处。

影响

受限模式下 ops_bash 整体不可用,且静默——模型会基于"命令成功"的虚假事实继续推理和改代码,比直接报错危险得多。目前只能靠模型主动怀疑并改用宿主 pwsh 兜底。

Activity

  1. CY020924 commented on Oct 23, 2026

    @CY020924

    同环境复现,另附一个新症状:ops_result.put 在 workspace-write 下同样必失败。
    Reproduced in a matching environment, plus one new symptom: ops_result.put also fails deterministically under workspace-write.

    环境 / Environment

    • Windows 11 build 26100 x64
    • DSH Desktop 2.0.18; dsh-ops 0.4.1; payload @dsh-ops/bash-win32-x64@0.2.1
    • msys-2.0.dll sha256 = 2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7 (matches provenance.json)
    • Session sandbox: workspace-write; workspace E:\DSH-WorkSpace

    复现 / Repro(2026-10-23 19:43–19:50 +08:00)

    • ops_bash returns status: completed, exitCode: 0, stdout.text: "\r\n", empty stderr.
    • sleep 2; echo DONE; exit 7 returns in 332 ms with exit 0 — the command never ran.
    • touch ops_probe_1.txt && exit 3 returns 0, and the file does not exist (verified with host PowerShell).
    • Passing the script via script_path (.sh) behaves the same.
    • Reproduced in an independent subagent session 3 times; printf 'X' and echo A; echo B both collapse to a single CRLF.
    • After switching the session to danger-full-access, the same commands work: sleep 1 measured 1282 ms, stdout/stderr and exit codes correct.

    新症状:ops_result.put / New symptom: ops_result.put

    • ops_result {action:"put", items:[...]} -> status: unknown, error.code: RESULT_UNKNOWN, message: "Worker returned invalid JSON; inspect audit records.", effectState: unknown, retry: never.
    • Reproduced with an empty array, one object, and two strings; no refId returned.
    • After switching to danger-full-access it succeeds immediately and returns ref://session-.../result/....
    • 疑似与 [BUG] Windows workspace-write:ops_bash 静默假成功(exit 0、stdout 空、命令未执行) #373 同源(结果存储写入被受限令牌拒绝),未进一步验证。 / Suspected to share the same root cause (result-store write denied under the restricted token); not verified further.

    另一个沙箱相关副作用 / Another sandbox side effect

    • A plain file created by ops_write_file (ops_probe.sh) carries Mandatory Label\Low Mandatory Level:(I)(NW); the host pwsh Remove-Item fails with access denied, while ops_remove succeeds. Not observed under danger-full-access.

    日志 / Log lines

    2026-10-23 19:40:42.549 [W] [dsh-ops-bash & host -power-shell] dsh-ops: bash tool teardown failed; continuing with the remaining sessions: session append cannot reenter while another append is being published
    2026-10-23 19:40:42.553 [W] [dsh-ops-bash & host -power-shell] dsh-ops: bash publication failed: session append cannot reenter while another append is being published
    

    不确定这两条是否与 #373 相关,供参考。 / Unsure whether these relate to #373; included for reference.

    结论 / Takeaway

    与 #373 完全一致:workspace-write 下 ops_bash 静默成功但未执行,模型会把失败当成功继续推理。目前唯一可行绕过是切到 danger-full-access(同时修复了本例中的 ops_result.put)。希望优先加一个 fail-closed 检查:未真正 spawn 时不要返回 exitCode: 0。

    Consistent with #373: under workspace-write, ops_bash silently "succeeds" without executing, so the model proceeds on a false success. The only workaround found is switching to danger-full-access (which also fixed ops_result.put here). A fail-closed check — never return exitCode: 0 when the handle never started — would prevent the silent variant.

  2. CY020924 commented on Oct 23, 2026

    @CY020924

    同环境复现,另附一个新症状:ops_result.put 在 workspace-write 下同样必失败。
    Reproduced in a matching environment, plus one new symptom: ops_result.put also fails deterministically under workspace-write.

    环境 / Environment

    • Windows 11 build 26100 x64
    • DSH Desktop 2.0.18; dsh-ops 0.4.1; payload @dsh-ops/bash-win32-x64@0.2.1
    • msys-2.0.dll sha256 = 2a89b7c31b323c42e2fb37900af087aeb27417694e630a210bc75b0369410bc7 (matches provenance.json)
    • Session sandbox: workspace-write; workspace E:\DSH-WorkSpace

    复现 / Repro(2026-10-23 19:43–19:50 +08:00)

    • ops_bash returns status: completed, exitCode: 0, stdout.text: "\r\n", empty stderr.
    • sleep 2; echo DONE; exit 7 returns in 332 ms with exit 0 — the command never ran.
    • touch ops_probe_1.txt && exit 3 returns 0, and the file does not exist (verified with host PowerShell).
    • Passing the script via script_path (.sh) behaves the same.
    • Reproduced in an independent subagent session 3 times; printf 'X' and echo A; echo B both collapse to a single CRLF.
    • After switching the session to danger-full-access, the same commands work: sleep 1 measured 1282 ms, stdout/stderr and exit codes correct.

    新症状:ops_result.put / New symptom: ops_result.put

    • ops_result {action:"put", items:[...]} -> status: unknown, error.code: RESULT_UNKNOWN, message: "Worker returned invalid JSON; inspect audit records.", effectState: unknown, retry: never.
    • Reproduced with an empty array, one object, and two strings; no refId returned.
    • After switching to danger-full-access it succeeds immediately and returns ref://session-.../result/....
    • 疑似与 [BUG] Windows workspace-write:ops_bash 静默假成功(exit 0、stdout 空、命令未执行) #373 同源(结果存储写入被受限令牌拒绝),未进一步验证。 / Suspected to share the same root cause (result-store write denied under the restricted token); not verified further.

    另一个沙箱相关副作用 / Another sandbox side effect

    • A plain file created by ops_write_file (ops_probe.sh) carries Mandatory Label\Low Mandatory Level:(I)(NW); the host pwsh Remove-Item fails with access denied, while ops_remove succeeds. Not observed under danger-full-access.

    日志 / Log lines

    2026-10-23 19:40:42.549 [W] [dsh-ops-bash & host -power-shell] dsh-ops: bash tool teardown failed; continuing with the remaining sessions: session append cannot reenter while another append is being published
    2026-10-23 19:40:42.553 [W] [dsh-ops-bash & host -power-shell] dsh-ops: bash publication failed: session append cannot reenter while another append is being published
    

    不确定这两条是否与 #373 相关,供参考。 / Unsure whether these relate to #373; included for reference.

    结论 / Takeaway

    与 #373 完全一致:workspace-write 下 ops_bash 静默成功但未执行,模型会把失败当成功继续推理。目前唯一可行绕过是切到 danger-full-access(同时修复了本例中的 ops_result.put)。希望优先加一个 fail-closed 检查:未真正 spawn 时不要返回 exitCode: 0。

    Consistent with #373: under workspace-write, ops_bash silently "succeeds" without executing, so the model proceeds on a false success. The only workaround found is switching to danger-full-access (which also fixed ops_result.put here). A fail-closed check — never return exitCode: 0 when the handle never started — would prevent the silent variant.

  3. Haoming9527 commented on Oct 24, 2026

    @Haoming9527
    Collaborator

    The current implementation was checked against main 662a388 and the pinned host subprocess contract. startBashExecution copies the explicit handle.done exit facts, and withBashStatus requires exactly exitCode === 0 with no error, permission failure or restriction. There is no fallback that converts a missing exit status to zero. The supported SubprocessHandle has no started field; adding that proposed check would reject valid handles rather than establish that Bash executed the command.

    The exact Desktop empty-success case has not been reproduced in this investigation, so no false-success fix is claimed. The provider contract says done reports the spawned command's outcome and rejects spawn/provider failures. If a wrapper returns zero without executing the target, the needed evidence is at that host subprocess/sandbox boundary: resolved argv, resolved policy, executable path, startup stderr and the raw completion outcome, compared with the same minimal command outside OPS. Empty output alone cannot distinguish the failure from a valid successful command. An output marker without a trusted protocol would also change or weaken the command contract.

    The independently reproduced shared-section failure has a narrow fix in #389 for #363. Runtime-directory access is a separate boundary described in #374. The supplemental run_in_background observation used the internal legacy definition; the host-facing projection already declares that argument when jobs are available.

    Closing this repository issue as not planned at the plugin layer, with the observations retained for a host-provider investigation. This is not a claim that the reported Desktop false success is fixed or that the earlier supported-host reproduction is invalid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions