如何知道 Claude Code、Codex 或 OpenCode 正在等你
Claude Code 通过三个钩子告诉你它在等你:AskUserQuestion 上的 PreToolUse、permission_prompt 类型的 Notification,以及最后一条消息以问题结尾的 Stop。请忽略 idle_prompt,它会跟在任何一条没人回应的回复之后到来。本文介绍 Foxl Code 为远程 Claude Code、Codex、OpenCode 和 Hermes 终端使用的对应关系、每个 CLI 无法上报的内容,以及怎样写出给智能体开销很小、又绝不会意外把内容送进模型的钩子。

本页内容
Claude Code 的钩子(hooks)会在特定事件发生时运行你配置好的命令,其中有三个能告诉你它正在等你。AskUserQuestion 工具上的 PreToolUse 钩子,会在它打开提问对话框时触发。类型为 permission_prompt 的 Notification 钩子,会在权限对话框打开几秒后触发。Stop 钩子在每次回复结束时触发,并在 last_assistant_message 中带上回复的文本,所以你可以检查回复是不是以问题结尾。请忽略 idle_prompt:只要一条回复之后没人输入,大约一分钟后它就会到来,并不只在提问之后。
Foxl Code 在远程终端中运行编程智能体,它正是用这三个钩子,把远程 Claude Code 终端标记为“在等用户”。从 v0.9.3-beta.5 起,同一个上报命令也会通过 Codex、OpenCode 和 Hermes 各自的钩子和插件读取它们的状态。下面依次介绍每个 CLI 的对应关系、各自的陷阱,以及怎样写钩子,才能让它给智能体带来的开销尽可能小,并且永远不会把文本漏进模型的上下文。目前还没有任何线上云端终端上报过提示;最后一节说明了哪些还没有验证。
为什么你看不出终端里的智能体在等你?
在远程终端里运行智能体的人,可能同时开着好几个,却一个都没在看。2026-10-21,一个远程 Claude Code 终端问了一个是非题,停在提示符那里,而 Foxl 自己的 API 却说没有任何任务在等人。Foxl Code 的侧边栏、列出等人处理的任务的“待你处理”列表,以及手机应用,全都没有动静。在这次改动之前,Foxl Code 里没有任何东西在监听这一刻。
读屏幕看起来像是解决办法,但很容易出问题。对外部读取者来说,思考中、已完成和正在提问这几种画面,只差 CLI 自己绘制的几个字符,而任何一个版本都可能把它们重新画过。有一次我们升级了随附的 CLI 版本,新版本画出的第一屏和我们的输入检查所等待的不一样,结果远程 Codex 和 OpenCode 终端在十天里悄无声息地忽略了所有输入。钩子是 CLI 自己对刚刚发生了什么的声明,数据格式有文档可查,所以我们监听的是钩子。
哪些 Claude Code 钩子表示“我在等你”?
我们的云端镜像固定使用 Claude Code 2.1.296。下文中标注为“从包中读取”的细节,来自该版本的 npm 包,而不是来自一个正在运行的终端。
| 钩子 | 含义 | 我们视为 |
|---|---|---|
PreToolUse,matcher 为 AskUserQuestion | 提问对话框即将打开;tool_input 中带有问题及其选项 | 一个问题,附带选项 |
Notification,matcher 为 permission_prompt | 权限对话框已打开约六秒,期间没有任何按键(从包中读取) | 一个权限请求 |
Stop | 回复已结束;last_assistant_message 是它的文本 | 文本以 ? 结尾则视为问题,否则视为一轮结束 |
UserPromptSubmit、PostToolUse、PostToolUseFailure、SessionEnd | 用户输入了内容、获批的工具已运行或失败、CLI 已退出 | 已回答 |
Notification,类型为 idle_prompt | “Claude 大约 60 秒前已完成回复,而你此后一直没有输入” | 忽略 |
大多数配置出错的地方就在 idle_prompt 这一行。它的消息读起来正是你想要的,但它会跟在任何一条没人接着输入的回复后面,所以把它当成问题,几乎每一轮之后都会触发提醒(在 Foxl 里,就是一条“待你处理”条目和一条手机通知)。我们注册 Notification 钩子时只用 permission_prompt 这个 matcher,脚本还会再检查一遍类型。对于 Stop 的判断,我们会先去掉末尾的空白、Markdown 强调符号、反引号、右括号和引号再检查,同时也接受中日韩输入法打出的全角 ?。
Claude Code 当前的文档还列出了一个专门的 PermissionRequest 事件,以及比这里更多的通知类型。我们没有核实 2.1.296 是否已经有这个较新的事件。permission_prompt 仍在当前的列表中,而它六秒的等待,正好能过滤掉有人在屏幕前直接回答的对话框。把这些钩子添加到 Claude Code 的用户级设置文件中,放在同一事件已配置的钩子之后,这样现有的钩子会继续运行:
{
"hooks": {
"PreToolUse": [{ "matcher": "AskUserQuestion",
"hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }],
"Notification": [{ "matcher": "permission_prompt",
"hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "report-prompt", "timeout": 10 }] }]
}
}这些钩子都调用同一个小脚本,也就是上报命令(上面的 report-prompt),它从环境中读取该任务的凭证。表示用户已经回答的事件,即 UserPromptSubmit 及其余几个(关闭事件),也以同样的方式注册,只是多了一道下文介绍的防护。
怎样写钩子,才能让它给智能体的开销尽可能小?
上报钩子在智能体自己的循环里运行,所以它的任何错误都会变成智能体的问题。我们最后总结出五条规则,每一条都来自文档或 2.1.296 包:
- 不要在 stdout 上输出任何内容。对大多数事件,Claude Code 只会记录钩子的 stdout,但对于
UserPromptSubmit和其他少数几个事件,“Claude Code 会把纯文本 stdout 作为 Claude 可以看到并据此行动的上下文加入”。上报脚本输出的一行状态信息,就会变成提示词的一部分。 - 始终以 0 退出。在可以阻止操作的事件上,退出码 2 会阻止该操作,其他非零退出码则会显示一条错误提示。在我们的脚本里,网络故障的代价只是 stderr 上的一行输出,别无其他。
- 同步运行,并设置较短的超时。钩子可以用
"async": true异步运行,但我们固定的这个版本每当一个异步钩子完成,都会在终端里打印一行;钩子在每次工具调用后都运行时,这就成了噪音。我们的钩子是同步的,设为timeout: 10,脚本给自己的请求三秒时间,稍后自行退出。在 Claude Code 上,实际开销是每次回复结束时启动一次 Node.js、发一个请求;即使 Foxl 的服务器无法访问,最多也只是大约三秒。 - 在 shell 里拦住高频事件。
PostToolUse在每次工具调用时都会触发。上报命令在上报一个未关闭的提示时,会创建一个标记文件。关闭钩子只在这个文件存在时才启动 Node.js,否则就把 stdin 读空,让 CLI 永远不会遇到断开的管道:
在没有未关闭的提示时,每次工具调用的开销就只是一次if [ -e /tmp/foxl-agent-prompt/open ]; then exec report-prompt; else cat >/dev/null; fitest -e。 - 不要把凭证放在命令行里。钩子继承 CLI 的环境,所以令牌从环境变量中读取,永远不会出现在
ps里。
Codex、OpenCode 和 Hermes 怎么表达这一点?
每个 CLI 都有自己的扩展点;我们按照镜像中固定的版本逐一读取了它们。Codex 有一个钩子引擎,接受 Claude Code 的钩子格式。OpenCode 和 Hermes(Nous Research 的智能体 CLI)在终端界面中不运行 shell 钩子;它们加载插件,而我们的插件会把一个规范化的事件交给同一个上报命令。AWS 的编程 CLI Kiro 则没有任何可以监听的事件。
| CLI(固定版本) | 提问 | 已回答 | 不上报 |
|---|---|---|---|
Codex 0.162.1,系统级 hooks.json | PermissionRequest;last_assistant_message 以 ? 结尾的 Stop | UserPromptSubmit、PostToolUse、Interrupt、SessionEnd | Plan 模式下的提问对话框 |
| OpenCode 1.19.0,服务器插件 | question.asked、permission.asked;回复以 ? 结尾时的 session.status idle | question.replied 或 rejected、permission.replied、session.status busy | - |
| Hermes 0.19.0,Python 插件 | 针对 clarify 的 pre_tool_call、pre_approval_request、以 ? 结尾的 post_llm_call | 针对 clarify 的 post_tool_call、post_approval_response、pre_llm_call、被中断的 on_session_end | - |
| Kiro 2.29.1 | 无 | - | 全部:没有提问或审批事件,它的 Stop 也不带回复文本 |
Codex。Codex 在运行你自己配置中的钩子之前会先审查它们:新增或修改过的钩子“会被标记为待审查,在获得信任之前一律跳过”。托管钩子设置在系统级配置中,按策略被信任,所以我们用这种方式安装自己的钩子。上报命令本身只安装在 Foxl Code 要求上报提示的终端里。在 0.162.1 上实测,非交互式的 codex exec 也会运行托管钩子,并且每次工具调用都会在输出中打印两行相关信息,所以对于 Foxl Code 用 codex exec 启动的运行,我们关闭了钩子。从 0.162.1 的源码中可以读到,Codex 的终端界面会显示任何运行超过 300 毫秒的钩子,所以上报命令会先判断是否有提示、写好标记文件,再把网络请求交给一个分离出去的自身副本。
Codex 的 Plan 模式提问对话框是有意不上报的,因为它的 PreToolUse 钩子在 Codex 判断这个问题是否被允许之前就会触发,而在 Plan 模式之外,Codex 会拒绝这个问题。在 0.162.1 上实测,钩子带着问题触发了,随后 Codex 回答“request_user_input is unavailable in Default mode”。Foxl Code 的终端以 Default 模式启动,所以上报这个钩子,就会把一个从来没有人被问到的问题推送到你的手机上。代价是,处于 Plan 模式的 Codex 终端,其提问对话框不会进入“待你处理”。
OpenCode。我们的插件监听 OpenCode 的事件总线。提问相关的事件并不在 OpenCode 插件文档的事件列表里,而是在 v1.19.0 的 schema 源码中,我们读的就是那里。idle 状态表示一轮结束,如果这一轮最后的回复以 ? 结尾,就算作一个问题。当提问或权限对话框打开时,状态变化会被忽略,所以智能体在对话框前后变为 busy 或 idle,都不会把它关掉。来自子智能体会话的事件会被跳过。
Hermes。和名字给人的印象相反,Hermes 的 on_session_end 在 0.19.0 上会在每一轮结束时、紧跟着回复触发。当我们的插件把它当作关闭事件时,它就撤回了这一轮刚刚提出的问题:12 个问题中有 7 个被立即关闭;还有 1 个问题在它自己的关闭事件之后才到达服务器,于是一直保持打开,再也没有东西能关闭它。现在只有被中断的会话结束才算作关闭,而且上报会按 Hermes 产生它们的顺序逐个发出。
Kiro 不在支持范围内,因为它的钩子没有提问或审批事件,它的 stop 数据也不带回复文本。钩子既无法打开一个提示,也无法区分问题和回答。唯一的信号是一个格式未公开的内部会话文件,我们选择不去解析它。
Foxl 知道之后会发生什么?
每条上报都发往 Foxl Code 服务器上的同一个端点,用该任务自己的凭证进行身份验证,并在同一个“待你处理”列表中打开或关闭一个条目,这个列表也收纳着智能体的其他问题。任务会被标记为等待你的回答,如图所示。

回复末尾的问题可以在“待你处理”或手机上回答。Foxl 会把答案作为一行输入到该任务的终端中,一秒后再单独发送回车键,因为包含回车的单个数据块会被当作粘贴,永远不会被提交。对于提问或权限对话框,Foxl 从不猜测终端界面期望的按键:它在“待你处理”中的卡片会列出选项,提示你在终端中回答,并提供一个打开终端的按钮。在终端中回答后,卡片会通过同样的钩子关闭。
只有可能被忽略的提示才会通知手机。在 Foxl 得知 15 秒后仍处于打开状态的提问或权限对话框,会发送一条通知,因为盯着终端的人几秒内就会回答。Claude Code 的权限对话框会在打开约六秒后才上报,所以它的通知大约在第 21 秒到达。以问题结尾的回复会显示在“待你处理”中,但不会推送通知,因为很多回复都以问题结尾。2026-10-21 的那个问题就属于这一类,所以现在它会出现在“待你处理”和任务上,但仍然不会让手机响起。

还有哪些没有验证?
在我们的测试中,真实的上报命令跑过了每个固定版本 CLI 的数据,插件在 Node.js 和 Python 下按固定的事件格式运行过,服务器端点也在 Cloudflare 运行时的本地副本中运行过。还没有做到的是:
- 来自真实云端终端的线上提问。截至 2026-10-23,生产环境的表中还没有来自这类终端的提示,所以在线上终端上的端到端检查仍有待完成。
- Claude Code 实际运行这些钩子。Claude Code 的细节,包括六秒的延迟和异步钩子打印的那一行,都是从 2.1.296 包中读取的,而不是在运行中的终端上看到的。我们有一个让该版本搭配这些钩子运行的测试,但还没有运行过。
- 其他 CLI 的终端。Codex、OpenCode 和 Hermes 的若干细节是从源码中读到的,包括 OpenCode 会以这些确切的名称发出提问事件;实际运行过的只有
codex exec。
如果你在自己的终端里运行智能体,最值得照搬的是 Claude Code 的钩子表。把这三个钩子接到任何能联系到你的地方,忽略 idle_prompt,不输出任何内容,以 0 退出,并在发送提醒前等几秒。关于 Foxl Code 的终端这一侧,我们的文章为什么每次按键曾经都发到了错误的机器上(英文)介绍了输入究竟是怎样到达远程智能体的。