Replies: 2 comments
|
matcher 语义先钉死:claude-code 方言下,非正则 matcher 是字面精确匹配,不是子串也不是正则 —— hook-protocol/tests/matcher.spec.ts:16-18 明确断言「Bash」匹配「Bash」、但不匹配「BashOutput」(codex 方言才是子串语义,见同文件 :39-41)。matcher 的主体是工具的真实注册名(hooks-codex 的回归测试注释也写明 payload 携带真实工具名,见其 coverage-cases:565-567)。 所以「写 Bash 选不中」最可能只有两个原因:① 实际注册的工具名不是「Bash」—— 比如大小写(bash vs Bash)或名字本身不同;② 配置没被加载(configPath 未生效)。二分法最快:先临时去掉 matcher(match-all)看钩子本身会不会触发 —— 会触发 ⇒ 是①,把工具清单里的真实名字对齐即可;不会触发 ⇒ 是②,查 carrier bundle 的 configPath 是否被读到。(matcher 语义与主体基于 0.1.5-rc.2 源码与测试核对;configPath 环节我无法在本机复现。) |
|
Thanks, that matches our reading of the code, and it narrows the bisect to one branch. We ran the matcher directly against the installed 0.1.5-rc.2: So it is your cause (1), not (2): the group matches, and the gate fires, as soon as the spelling aligns. The reason we are confident it is not a config-loading problem is that the only thing that changed between "never runs" and "runs" was the matcher string. To be explicit: we are not asking to loosen the literal semantics. The literal path is fine and we now read it the same way. The gap is the dialect boundary. This adapter carries Claude Code's hook format, and Claude Code's documented matcher for its shell tool is Two changes would close it without touching the semantics:
On the secondary point: confirmed on our side too. |
Uh oh!
There was an error while loading. Please reload this page.
Environment: dsh
0.1.5-rc.1, nodev22.22.0, Linux.A project-level
.dsh/hooks.jsonin the Claude Code format, activated through a carrier bundle that inserts@deepseek-ai/dsh-hooks-claude-codewith an absoluteconfigPath:{ "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python3 .dsh/hooks/gate.py", "timeout": 10 } ] } ] }Expected: the command runs before every shell tool call.
Actual: it never runs. No error, no warning, nothing in the log, and
dsh --profile web --dump-configshows the row composed correctly.Root cause, three verifiable facts:
bash, notBash:dsh-tool-bashhasname: "bash".dsh-hooks-claude-codebuilds the matcher query astool_name: exec.name.matchesMatcherdoespattern.split("|").includes(query)whenCLAUDE_LITERAL(/^[A-Za-z0-9_|]+$/) matches.So
"Bash"is compared against"bash"and the whole group is skipped.Minimal reproduction:
Impact, and why "silent" is the defect. Claude Code's documented matcher for the shell tool is
Bash, so a hooks file written from the documentation selects nothing on this adapter. The failure is invisible at every layer: the config parses, the plugin composes, the group never matches, and a[ -f "$h" ]-guarded command exits 1, which the protocol reads as "no decision".Measured cost on our side: two pre-tool gates (one refusing a whole
pytest tests/, one refusing an unlockedpytest tests/contracts/, 4,042 tests) were silently absent for as long as they existed. A five-minute suite ran unlocked beside another agent's suite and both doubled (9:13 against 4:48 isolated, and 13:20 for the suite beside it against a 7:05 baseline) before we noticed.Suggested fixes, any one of which closes it:
bash→Bash, and the same for the rest of the Claude Code vocabulary this dialect documents), so matchers written per those docs keep working.PreToolUsegroup selected nothing in a session: a matcher that names a known Claude Code tool and matched no call is a wiring bug, and today it looks exactly like a gate with nothing to refuse.Workaround we ship:
"matcher": "bash|Bash", which stays on the literal path and matches either spelling.Secondary observation, same family:
@deepseek-ai/dsh-hooks-claude-codedeclares nodsh.bundle, so listing it as a dependency composes its row in--dump-configwhile the hook process never starts; activation required a carrier bundle in the project. If that is intended, one line in the package README would save the next person the evening it cost us.All reactions