随便一个插件的hook就可以把IDE 对话框卡死!!!

# [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 后会被覆盖,非长久之计。

您好!

关于您反馈的问题,建议您点击 Qoder 界面右上角的「问题上报」按钮进行提交,并发送下反馈码。我们会尽快分析并排查处理。感谢您的支持!