# [Bug 反馈] Qoder IDE 执行插件 Hook 时未注入 CLAUDE_PLUGIN_ROOT,导致 Hook 误阻断用户操作
## 概要
Qoder IDE(Electron 窗口内的对话)在执行插件声明的 Hook 命令时,使用 `session-cached` 环境变量集合,其中**不包含 `CLAUDE_PLUGIN_ROOT` / `QODER_PLUGIN_ROOT`**。凡是 Hook 命令中引用 `${CLAUDE_PLUGIN_ROOT}` 的插件,变量会展开为空字符串,命令以错误路径执行失败(exit 2),进而被判定为 “Hook blocked”,**阻断用户的正常对话操作(如上传文件、提交 prompt)**。
同一插件、同一 Hook 在 qodercli(终端 CLI)中运行完全正常,说明 CLI 侧正确注入了该变量,问题仅存在于 IDE 侧的 Hook 执行器。
## 环境信息
| 项 | 值 |
|—|—|
| 操作系统 | macOS (darwin 25.5.0, arm64) |
| qodercli 版本 | 1.0.45 (global) |
| 复现日期 | 2026-07-30 |
| 触发插件 | apex-delivery-plugin@apex-marketplace v0.1.14(user scope,经 headless_plugin_install 安装) |
| Hook 配置文件 | `~/.qoder/plugins/cache/apex-marketplace/apex-delivery-plugin/0.1.14/hooks/hooks.json` |
## 复现步骤
1. 安装任意在 `hooks.json` 中使用 `${CLAUDE_PLUGIN_ROOT}` 引用自身脚本的插件,例如:
```json
{
“type”: “command”,
“command”: “python3 \”${CLAUDE_PLUGIN_ROOT}/skills/gate/scripts/gate-dispatcher.py\“”
}
```
2. 在 **Qoder IDE 的对话窗口**中提交消息或上传文件,触发 UserPromptSubmit Hook。
3. 观察对话被阻断,报错:
```
Hook 阻止:python3: can’t open file ‘/skills/gate/scripts/gate-dispatcher.py’: [Errno 2] No such file or directory
```
4. 对照:在终端用 qodercli 触发相同 Hook,一切正常(exit_code=0)。
## 实际结果 vs 期望结果
- **实际**:`${CLAUDE_PLUGIN_ROOT}` 展开为空 → 脚本路径变为 `/skills/gate/scripts/gate-dispatcher.py` → python3 报 Errno 2 → Hook exitCode=2 → UserPromptSubmit 被 block,用户消息无法提交。
- **期望**:IDE 与 CLI 行为一致,在执行插件 Hook 前注入 `CLAUDE_PLUGIN_ROOT`(及 `QODER_PLUGIN_ROOT`)指向插件安装根目录。
## 关键日志证据
### IDE 侧(失败)
来源:`~/Library/Application Support/Qoder/logs/20260729T174804/window2/hooks.log`
```
2026-07-30 13:52:06.037 [warning] Hook blocked (event=UserPromptSubmit, reasons=python3: can’t open file ‘/skills/gate/scripts/gate-dispatcher.py’: [Errno 2] No such file or directory
2026-07-30 13:52:06.037 [info] Hook execute completed (event=UserPromptSubmit, exitCode=2, command=python3 “${CLAUDE_PLUGIN_ROOT}/skills/gate/scripts/gate-dispatcher.py”, cost=42.714ms)
2026-07-30 13:52:09.844 [info] Hook execute started (command=python3 “${CLAUDE_PLUGIN_ROOT}/skills/gate/scripts/gate-dispatcher.py”,
actualCmd=/bin/zsh -c python3 “${CLAUDE_PLUGIN_ROOT}/skills/gate/scripts/gate-dispatcher.py”,
cwd=/Users/tbsg/.qoder/plugins/cache/apex-marketplace/apex-delivery-plugin/0.1.14,
envSource=session-cached, envCount=38)
```
要点:
- `envSource=session-cached, envCount=38`(另有 envCount=45 的记录),该环境集合中无 `CLAUDE_PLUGIN_ROOT`;
- `cwd` 已被正确设置为插件根目录,但命令中的变量无人展开;
- 目标脚本实际存在于 `~/.qoder/plugins/cache/apex-marketplace/apex-delivery-plugin/0.1.14/skills/gate/scripts/gate-dispatcher.py`。
### CLI 侧(正常,对照组)
来源:`~/.qoder/logs/runs/2026-07-30T13-50-38-136+08-00-2hux3y-p91505/qodercli.log`
```
hook.started hook_name=“UserPromptSubmit” source=“plugins”
display_text=“python3 \”${CLAUDE_PLUGIN_ROOT}/skills/gate/scripts/gate-dispatcher.py\“”
plugin_id=“apex-delivery-plugin@apex-marketplace”
plugin_root=“/Users/tbsg/.qoder/plugins/cache/apex-marketplace/apex-delivery-plugin/0.1.14”
hook.finished … success=true exit_code=0
```
### 旁证
同机安装的 pangolin-toolkit 插件因使用兜底写法而在 IDE 中不受影响:
```
python3 “${QODER_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}/scripts/hook_user_prompt.py”
```
变量缺失时回退到 `.`,而 IDE 恰好把 cwd 设为插件根目录,因此能正常执行。这进一步印证问题就是"IDE 未注入变量"而非路径/安装问题。
## 影响范围
- 所有在 Hook 命令中直接引用 `${CLAUDE_PLUGIN_ROOT}`(无兜底)的插件,在 IDE 对话场景下 Hook 全部失败。
- 若失败的是 UserPromptSubmit / PreToolUse 等阻断型事件,会**直接阻断用户正常使用对话**(本例即上传文件被拒)。
- CLI 与 IDE 行为不一致,插件作者难以感知:插件在 CLI 下测试通过,上了 IDE 才暴雷。
## 修复建议
1. **根治**:IDE 的 Hook 执行器在构造子进程环境时,为插件来源的 Hook 注入 `CLAUDE_PLUGIN_ROOT`(并同步注入 `QODER_PLUGIN_ROOT`),取值与日志中已有的 `cwd`/plugin_root 一致;`session-cached` 环境缓存不应丢弃这类逐 Hook 动态变量。
2. **防御**(可并行):Hook 因命令自身错误(如 exit 2、命令不存在)失败时,与插件逻辑主动返回 `“decision”:“block”` 区分开,避免基础设施类失败直接阻断用户操作;至少在错误提示中给出 Hook 来源插件与配置文件路径,便于用户定位。
3. **文档**:在插件开发文档中明确 IDE / CLI 两种宿主对 Hook 环境变量的注入行为,并推荐 `${QODER_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}` 兜底写法。
## 用户侧临时规避(已验证有效)
将插件缓存中 `hooks.json` 的所有命令改为兜底写法:
```
python3 “${QODER_PLUGIN_ROOT:-${CLAUDE_PLUGIN_ROOT:-.}}/skills/gate/scripts/gate-dispatcher.py”
```
已在本机验证:清空两个环境变量、cwd 设为插件根目录时脚本可正常找到并执行(exit=0)。缺点:插件 autoUpdate 后会被覆盖,非长久之计。