Skip to content

[v0.39.2] Windows 自有 bash 工具永不注册:模块硬 inject 了宿主不存在的 workingDirectory(cordis 静默 INACTIVE + 假阳性日志) #27

Closed

Description

@largoalladonzella

TL;DR:DSH Desktop 0.2.0-rc.2 上,插件自有的 bash 工具永不注册且零错误日志。根因:src/bash-tool.js 把 workingDirectory 写成了硬 inject,而该宿主构建里没有这个服务;cordis 对无法满足的 inject 会让 fiber 永久 INACTIVE(apply() 从不执行、不报错),而包装层仍然打印 own bash tool activated for this agent(假阳性)。附带第二个缺陷:同一服务被用来取会话 cwd,导致修好注册后命令跑在宿主进程目录。两个缺陷都有本机补丁实测通过。

报告者环境:Windows 11 (26100) / DSH Desktop 0.2.0-rc.2 / dsh-gitbash-shell 0.39.2 / Node v24.19.0;证据均为本机实测与宿主源码(app.asar / cordis / 官方 dsh-tool-bash)复核。

相关但不同的既有 issue:#25 是 v0.35.1 的 exists is not a function(旧架构),与本条无关。

状态:两个缺陷均已定位并在本机补丁验证通过(A 注册失败 / B cwd 错位);补丁只改插件本地文件,未提交上游。
环境:Windows 11 build 26100 / DSH Desktop 0.2.0-rc.2 / dsh-gitbash-shell 0.39.2 / Node v24.19.0 / Git for Windows C:\Program Files\Git\bin\bash.exe


摘要

本机(0.2.0-rc.2,插件兼容矩阵自称 compatible)下,插件自有的 bash 工具永远不出现,而且没有任何错误日志。两个独立缺陷叠在一起:

缺陷 后果
A 自有 bash 模块把 workingDirectory 写成硬 inject,而本机 DSH 构建里没有这个服务 cordis 让 fiber 永久 INACTIVE → apply() 从不执行 → 工具从不注册;包装层仍打印 own bash tool activated for this agent(假阳性)
B 该模块用 ctx.workingDirectory.ensure(agent, signal) 取会话 cwd;本机官方 tool-bash 用的是 exec.agent.session.header.cwd 修掉 A 之后工具能注册,但命令落在宿主进程目录而不是会话工作区(pwd = C:\Users\adrian\.dsh\profiles\desktop,应为 D:\work\workfiles\vibrcoding\dshtest)

复现

  1. profile patch:- id: gitbash-shell(pwshTool: false,variantPresets 开/关都试过)。
  2. 重启宿主 → 新建/继续会话(触发 agent/created)。
  3. 期望工具面出现 bash。实际:只有 pwsh(打开开关后)与 gb_*;bash 永远缺席,且 host stdout 没有任何 activation failed:
[gitbash-shell] inline tool patch armed (own bash tool over a private Git Bash executor; official pwsh executor untouched)
[gitbash-shell] own bash tool ready (private Git Bash executor at C:/Program Files/Git/bin/bash.exe; official pwsh executor untouched)
[gitbash-shell] own bash tool activated for this agent (Git Bash execution; official pwsh tool real but hidden by default)
[gitbash-shell] FastCtx bridge active (@dsh-ops/fastctx-win32-x64): gb_glob, gb_grep, gb_inspect_local_file, gb_replace

第三行是假阳性:它只证明挂载对象建好了。

缺陷 A 的根因链(逐环有据)

A1. 自有模块 inject 里带 workingDirectory

node_modules/dsh-gitbash-shell/src/bash-tool.js

return {
  inject: ['tools', 'systemPrompt', 'shellEnv', 'workingDirectory'],   // ← 322
  apply(ctx, config = {}) {

A2. 本机 0.2.0-rc.2 里没有这个服务

对整个 resources/app.asar(12967 条目)做字节级搜索:

$ node scripts/asar.mjs find app.asar "workingDirectory" 30   → # 0 hit(s)
$ node scripts/asar.mjs find app.asar "'workingDirectory'" 5  → # 0 hit(s)
$ node scripts/asar.mjs find app.asar "workingDir" 8          → # 0 hit(s)

主机 Service 目录:

listService { service: "workingDirectory" } → Error: no catalogued Service named "workingDirectory"
listService { service: "shellEnv" }         → 完整契约(对照组,查询本身有效)

同宿主官方 @deepseek-ai/dsh-tool-bash 的 inject 不含它:

// dsh/node_modules/@deepseek-ai/dsh-tool-bash/lib/index.js:223
const inject = ["tools", "shell", "systemPrompt", "shellEnv"];

A3. cordis:inject 解析不全 ⇒ fiber 永久 INACTIVE、静默、await 立即返回

node_modules/@deepseek-ai/cordis/lib/index.js

_refresh() {                                  // :1317
  let epoch = "";
  for (const name of Object.keys(this.inject)) {
    const impl = this._store[name];
    if (!impl) { epoch = INACTIVE; break }    // 任一未解析 ⇒ INACTIVE
    epoch += ":" + impl.fiber.uid;
  }
  this._setEpoch(epoch);
}
_setEpoch(epoch) {                            // :1330
  const oldEpoch = this._runner.epoch;
  if (epoch === oldEpoch) return;             // 构造期即 INACTIVE ⇒ 直接返回,无 inertia
  ...
}
async await() {                               // :1399
  while (this.inertia) await this.inertia;    // INACTIVE fiber 无 inertia ⇒ 立即返回
  if (this._error) throw this._error;
  return this;
}

ctx.plugin() 返回的 thenable 就是 fiber.await()(:1636-1640)。于是:

  • apply() 从不执行 ⇒ 没有 ctx.tools.register('bash');
  • 不抛错、不记日志 ⇒ 与「无 activation failed」一致;
  • await fiber 立即 resolve ⇒ src/inline-tools.js:286-289 的 plugin() → await → log('activated') 照常打印。

A4. 为什么以前能用

v0.38.4 日志显示当时挂的是官方 tool-bash(其 inject 由宿主决定,本机可满足);v0.39.0 route B 换成自有模块后,注入列表照抄的是更新版宿主。而 package.json 写着:

"dsh": { "compatibility": { "dsh": ">=0.1.7-rc",
  "dshReleases": { "0.2.0-rc.2": "compatible", "0.2.1-alpha.1": "compatible", "0.2.1-alpha.2": "compatible" } } }

缺陷 B:cwd 接缝也对不上

本机官方实现(dsh-tool-bash/lib/index.js:293):

function resolveWorkdir(modelWorkdir, exec, policyWorkspaceRoot) {
  const headerCwd = exec.agent?.session.header.cwd;
  const sessionCwd = policyWorkspaceRoot ?? headerCwd;
  if (modelWorkdir === void 0) return sessionCwd;
  ...
}

插件用的是 ctx.workingDirectory.ensure(...)(本机不存在)。实测:补丁 A 之后 bash 出现并可执行,但

$ bash -lc 'pwd'
/c/Users/adrian/.dsh/profiles/desktop        # 宿主进程目录
$ pwsh -Command 'Get-Location'
D:\work\workfiles\vibrcoding\dshtest         # 会话工作区(正确)

本机验证补丁(diff,三处)

--- a/src/bash-tool.js
+++ b/src/bash-tool.js
@@
-    inject: ['tools', 'systemPrompt', 'shellEnv', 'workingDirectory'],
+    // workingDirectory is not a service on every host this plugin claims
+    // compatibility with; read it optionally at the use site instead.
+    inject: ['tools', 'systemPrompt', 'shellEnv'],
@@
-          const cwd = exec.agent === undefined ? undefined : await ctx.workingDirectory.ensure(exec.agent, exec.signal)
+          const workingDirectory = typeof ctx.get === 'function' ? ctx.get('workingDirectory') : undefined
+          const cwd = exec.agent === undefined
+            ? undefined
+            : workingDirectory !== undefined
+              ? await workingDirectory.ensure(exec.agent, exec.signal)
+              : policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd

(第二段同时修 A 的属性访问与 B 的 cwd 接缝;node --check 通过。)语义:有 workingDirectory 的新宿主行为不变;没有的宿主退回本机官方 tool-bash 的同一条接缝。

Verification

缺陷 A(已完成)

  • 补丁后重启 → host Tool Inspect 出现 bash
  • 真机执行成功:
$ echo "MSYSTEM=$MSYSTEM"; uname -s; pwd; git --version; echo "shell=$BASH_VERSION"; (exit 3); echo "propagated-exit=$?"
MSYSTEM=MINGW64
MINGW64_NT-10.0-26100
/c/Users/adrian/.dsh/profiles/desktop
git version 2.55.0.windows.3
shell=5.3.15(1)-release
propagated-exit=3

(退出码传递正确;pwd 见缺陷 B)

缺陷 B(已完成)

  • 补 cwd 接缝后重启 → 默认目录回到会话工作区:
$ pwd
/d/work/workfiles/vibrcoding/dshtest
  • 相对 workdir 按会话工作区解析(入参 workdir: "scripts"):
$ pwd; ls -1
/d/work/workfiles/vibrcoding/dshtest/scripts
asar.mjs

后台任务通道(顺带验证)

$ (bash tool, run_in_background: true)
for i in 1 2 3; do echo "tick-$i from $MSYSTEM"; sleep 1; done; echo done
# job_output:
tick-1 from MINGW64
tick-2 from MINGW64
tick-3 from MINGW64
done
[status: completed, exit code: 0]

host 日志(补丁后,无异常行)

[gitbash-shell] own bash tool ready (private Git Bash executor at C:/Program Files/Git/bin/bash.exe; official pwsh executor untouched)
[gitbash-shell] own bash tool activated for this agent (Git Bash execution; official pwsh tool real but hidden by default)
[gitbash-shell] FastCtx bridge active (@dsh-ops/fastctx-win32-x64): gb_glob, gb_grep, gb_inspect_local_file, gb_replace

注意:这两行在补丁前是一样的 —— 它们无法区分「注册成功」与「fiber 静默 INACTIVE」,这正是建议 3 的依据。

建议的上游修法

  1. 不要硬 inject 宿主可能没有的服务:workingDirectory 用 ctx.get() 可选读取(或按宿主版本探测后决定 inject),与本仓库已有的 hostExtras 探测同思路;
  2. cwd 走本机已有的接缝:policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd,有 workingDirectory 时优先;
  3. 消除假阳性日志:apply() 真正跑完后再打印「activated」,最好用 tools.schemas(agent) 回读 bash 是否在册,不在册就打明确的失败行——本次排查最大的成本就是这行成功日志掩盖了静默 INACTIVE;
  4. 顺带:兼容矩阵把 0.2.0-rc.2 标为 compatible,但该宿主既无 workingDirectory 也无对应接缝,建议复核矩阵口径。

取证工具

scripts/asar.mjs(零依赖,list / read / find / extract)可直接对 resources/app.asar 做字节级取证,复现 A2 的三条搜索即可。

Activity

  1. KannaKuron commented on Oct 13, 2026

    @KannaKuron
    Owner

    感谢这份极其扎实的报告——asar 字节级取证、cordis _refresh/_setEpoch 逐环源码引用、两版宿主官方 tool-bash 的 inject 对照,加上本机验证补丁,让根因确认只花了一次源码对照。三个缺陷全部核实属实,已按你建议的三条修法实现,随 v0.39.3 发布(commit ba09867):

    1. 硬 inject 收敛(缺陷 A):自有模块 inject 收敛为 ['tools', 'systemPrompt', 'shellEnv'],workingDirectory 改为使用点 ctx.get() 可选读取——不再让任何一个宿主缺失的服务静默杀死整个模块 fiber。
    2. cwd 接缝双向兼容(缺陷 B):有 workingDirectory 的宿主走 ensure()(与新版官方一致,行为不变);没有的宿主退回 rc.2 官方 resolveWorkdir 的同一条接缝(policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd)——命令不再落在宿主进程目录。
    3. 假阳性日志消除(建议 3):激活成功现在必须经 tools.schemas(agent) 回读确认 bash 真正在册;不在册打显式失败行(点名 INACTIVE 可能性 + 请携带 dsh 版本报告)——成功日志不再是唯一证据,也不再有「无错误的全灭」。
    4. 兼容矩阵口径:修复落地后 0.2.0-rc.2: compatible 重新成立(此前该标注对自有 bash 工具确实名不副实,正如你指出的)。

    这个教训已写入仓库 AGENTS.md(§4l 追记⑧):镜像宿主模块时,inject/服务消费面必须取兼容矩阵内最老宿主的形状或可选化——「宿主新版本有」不等于「矩阵内都有」;激活/注册类日志必须回读验证,成功日志本身不是证据。

    新增回归测试:无 workingDirectory 的最小 ctx 上 apply() 照常注册 + cwd 回退正确;有服务的宿主 ensure() 接缝不变;源码断言锁死硬 inject 列表与回读门控日志。scripts/asar.mjs 这类取证工具的思路也很有价值,感谢公开。

    升级到 0.39.3 即可(注意 pnpm/registry 的 minimumReleaseAge 冷却)。修复若在你的 rc.2 环境复验通过,欢迎回帖确认——你上一轮的真机验证补丁正是本次实现的蓝本。

  2. largoalladonzella commented on Oct 14, 2026

    @largoalladonzella
    Author

    复验:0.39.3 在 DSH 0.2.0-rc.2 上已完全修复 ✅

    环境:Windows 11 (26100) / DSH Desktop 0.2.0-rc.2 / dsh-gitbash-shell 0.39.3 / Node v24.19.0。

    确认跑的是干净的上游代码:插件目录 10:36:40 更新到 0.39.3,宿主 10:37:46 启动(晚于更新);grep -c "LOCAL PATCH" src/bash-tool.js → 0,报告里那份本机补丁已被完全取代,node_modules 无需再维护。

    实测结果(官方 0.39.3):

    $ echo "MSYSTEM=$MSYSTEM"; uname -s; pwd; echo "shell=$BASH_VERSION"; (exit 3); echo "propagated-exit=$?"
    MSYSTEM=MINGW64
    MINGW64_NT-10.0-26100
    /d/work/workfiles/vibrcoding/dshtest          # ← 缺陷 B 已修:会话工作区,不再是宿主进程目录
    shell=5.3.15(1)-release
    propagated-exit=3                             # ← 退出码传递正常
    
    $ (tool workdir="scripts") pwd
    /d/work/workfiles/vibrcoding/dshtest/scripts  # ← 相对 workdir 正确
    
    • 工具面:bash 出现在 tools.schemas(agent) 中,gb_* / pwsh 行为不变;
    • 后台任务:run_in_background: true → 流式 tick 输出 + [status: completed, exit code: 0];
    • host 日志:无异常行;新增的那条 MSYS2/受限沙箱说明(0.39.3)按预期打印。

    三条修法都核对了源码:inject 收敛为 ['tools','systemPrompt','shellEnv'](bash-tool.js:344)、cwd 走 policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd(:518-523)、以及激活后 tools.schemas(agent) 回读校验后才打成功日志(inline-tools.js:289-300)。

    特别感谢第 3 条 —— 这次的排查成本几乎全部来自那行「成功形态」的日志掩盖了静默 INACTIVE;有了回读校验,同类问题以后会自己报出来,不会再让人查一下午。

    从我的角度看 #27 可以关闭。谢谢这么快的一次源码对照 + 发版 🙏

  3. KannaKuron commented on Oct 14, 2026

    @KannaKuron
    Owner

    按你的复验关闭——rc.2 上三个缺陷(注册失效 / cwd 错位 / 假阳性日志)全部确认修复,感谢这次带证据链的复验(干净上游代码核验 + 逐条源码对照 + 实测矩阵)。

    你指出的「排查成本来自成功形态日志掩盖静默 INACTIVE」正是这次修复的核心动机之一——回读校验这条以后会让同类问题自己报错,不再靠人肉排查。asar 取证脚本与根因链的质量也值得一提,欢迎继续。

    修复随 v0.39.3 发布(ba09867),后续吸收 PR #28 形态的 scope 释放改进在 main(114dcde),将随下个版本发布。

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