Repository navigation
[v0.39.2] Windows 自有 bash 工具永不注册:模块硬 inject 了宿主不存在的 workingDirectory(cordis 静默 INACTIVE + 假阳性日志) #27
Description
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 WindowsC:\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) |
复现
- profile patch:
- id: gitbash-shell(pwshTool: false,variantPresets开/关都试过)。 - 重启宿主 → 新建/继续会话(触发
agent/created)。 - 期望工具面出现
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
ToolInspect 出现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 的依据。
建议的上游修法
- 不要硬 inject 宿主可能没有的服务:
workingDirectory用ctx.get()可选读取(或按宿主版本探测后决定 inject),与本仓库已有的hostExtras探测同思路; - cwd 走本机已有的接缝:
policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd,有workingDirectory时优先; - 消除假阳性日志:
apply()真正跑完后再打印「activated」,最好用tools.schemas(agent)回读bash是否在册,不在册就打明确的失败行——本次排查最大的成本就是这行成功日志掩盖了静默 INACTIVE; - 顺带:兼容矩阵把
0.2.0-rc.2标为 compatible,但该宿主既无workingDirectory也无对应接缝,建议复核矩阵口径。
取证工具
scripts/asar.mjs(零依赖,list / read / find / extract)可直接对 resources/app.asar 做字节级取证,复现 A2 的三条搜索即可。
Activity
fix: host-service compatibility for the private bash tool, sidebar na…
KannaKuron commentedon Oct 13, 2026
感谢这份极其扎实的报告——asar 字节级取证、cordis
_refresh/_setEpoch逐环源码引用、两版宿主官方 tool-bash 的 inject 对照,加上本机验证补丁,让根因确认只花了一次源码对照。三个缺陷全部核实属实,已按你建议的三条修法实现,随 v0.39.3 发布(commit ba09867):inject收敛为['tools', 'systemPrompt', 'shellEnv'],workingDirectory改为使用点ctx.get()可选读取——不再让任何一个宿主缺失的服务静默杀死整个模块 fiber。workingDirectory的宿主走ensure()(与新版官方一致,行为不变);没有的宿主退回 rc.2 官方resolveWorkdir的同一条接缝(policy?.workspaceRoot ?? exec.agent?.session?.header?.cwd)——命令不再落在宿主进程目录。tools.schemas(agent)回读确认bash真正在册;不在册打显式失败行(点名 INACTIVE 可能性 + 请携带 dsh 版本报告)——成功日志不再是唯一证据,也不再有「无错误的全灭」。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 环境复验通过,欢迎回帖确认——你上一轮的真机验证补丁正是本次实现的蓝本。
fix: a failed bash registration now throws and disposes the agent sco…
largoalladonzella commentedon Oct 14, 2026
复验: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):
bash出现在tools.schemas(agent)中,gb_*/pwsh行为不变;run_in_background: true→ 流式 tick 输出 +[status: completed, exit code: 0];三条修法都核对了源码:
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 可以关闭。谢谢这么快的一次源码对照 + 发版 🙏
KannaKuron commentedon Oct 14, 2026
按你的复验关闭——rc.2 上三个缺陷(注册失效 / cwd 错位 / 假阳性日志)全部确认修复,感谢这次带证据链的复验(干净上游代码核验 + 逐条源码对照 + 实测矩阵)。
你指出的「排查成本来自成功形态日志掩盖静默 INACTIVE」正是这次修复的核心动机之一——回读校验这条以后会让同类问题自己报错,不再靠人肉排查。asar 取证脚本与根因链的质量也值得一提,欢迎继续。
修复随 v0.39.3 发布(ba09867),后续吸收 PR #28 形态的 scope 释放改进在 main(114dcde),将随下个版本发布。