Claude Code 等待授权为何难以文本匹配
这篇德语文章与列表中的西语条目介绍同一项 osFoundry 检测实践:终端画面依靠回车和光标控制拼接,原始字节不一定包含可见空格或换行。作者结合 11 份真实捕获样本解释权限等待检测的陷阱。
AI 深度解读
判断 Claude Code 是否在等你批准命令,难点居然藏在终端怎么画字上。osFoundry 的做法是先还原屏幕,再识别权限提示;能接入 Hooks 或协议时,再结合 Agent 主动上报的状态。
这篇文章值得读,不是因为正则有多巧,而是它拆开了三个经常被混淆的状态:正在工作、已经完成、等待授权。作者用真实终端录制和模拟实验说明,仅凭输出停了、进程睡了、出现问号,都可能判断错。
为什么必须读屏幕,不能直接搜输出
原文展示了 Claude Code 2.1.303 的真实终端字节。在「Do」和「you」之间,没有空格,只有 \x1b[5G:它把光标移到第 5 列,屏幕上没有写过的单元格自然呈现为空白。
终端输出不是排好版的日志,而是一串绘制指令。
换行同样如此。作者保存的 11 份 Claude Code 录制,每份 2,099 到 7,672 Bytes,全部没有 \n。界面通过 \r 回到行首,再用 \x1b[1B 把光标向下移动。
直接删除转义序列,会得到粘在一起的 Doyouwanttoproceed?;如果再只保留最后一个 \r 后的内容,前面的提示就丢了。
作者把同一套规则分别用于「清理后的最后 8 KB」和「32×120 的模拟屏幕」。结果是前者答对 5/11,后者答对 11/11。前者答对的全是 idle,因为没有规则命中时,默认结果就是 idle。
osFoundry 为每个 Session 保留 vt100::Parser,识别的是 screen_text()。屏幕上方的 200 行历史不参与判断,否则已经回答、滚出当前画面的权限提示,可能让状态一直停在 blocked。
作者的 Python 示例模拟器约 100 行,支持文本、\r \n \b、光标移动 A B C D E F G H f d 和擦除 J K,跳过颜色、模式及 OSC 标题。它按一个字符占一格处理,宽字符可能造成列偏移,但对这批按整行匹配的规则已经够用。
需要识别的是用户此刻看到的界面,而不是终端曾经输出过什么。
权限规则为什么必须排在工作规则前面
osFoundry 用每个 Agent 一份 TOML 文件描述规则。下面这份 Claude Code 配置,解决的是「哪些界面文字代表哪种状态」:
[[rules]]
state = "blocked"
any = [
'Do you want to (proceed|continue|make this edit|create)',
'❯\s*1\.\s*Yes',
'Esc to cancel · Tab to amend',
'Enter to select · ↑/↓ to navigate',
'Exit plan mode\?',
]
[[rules]]
state = "working"
any = ['esc to interrupt']
[[rules]]
state = "idle"
any = ['\? for shortcuts', '(?:⏸|⏵⏵) [a-z ]+ mode on']
要看的是第一条 state = "blocked":CompiledManifest::evaluate 采用首次命中即返回的方式,而权限提示出现时,页脚仍可能显示 esc to interrupt。先判断 working,就会把真正的授权请求盖过去。
没有规则命中时返回 idle。这里的 idle 是分类兜底,不等于已经证明任务完成。
匹配词也要克制。作者删除了 Would you like to,因为那是 Codex 的提示措辞,而 Claude 普通回复经常包含 Would you like me to…。Agent 在正文里提问,和终端打开授权菜单,是两回事。
执行频率也有取舍。runtime/mod.rs 的 SWEEP_INTERVAL 是 700 ms,每轮遍历所有 Pane。Spinner 一秒重画多次,每次写入都分类,会重复运行大量正则。面向人的状态徽标,几百毫秒检查一次已经有实用价值。
规则和执行器分离,则让提示文案变化可以通过更新数据文件处理,不必每次修改程序发布。
静默计时和进程状态,为什么都分不清
作者所在的 Mac 没安装 claude Binary,因此动态实验使用 fake_agent.py,不能当成真实 Claude Code 的端到端测试。真实字节只用于前面的 Fixture 验证。
模拟器复现了几个关键行为:Spinner 以 10 Hz 重画,每帧 60 Bytes;工具执行时静默 0.5 到 6 s;权限提示按词定位,并在回答后用光标上移和 \x1b[J 擦除。它还会输出以 ? for shortcuts 结尾的空闲界面,有时正文包含 Would you like me to add a regression test?。
Supervisor 在真实 PTY 中每 50 ms 记录一次判断。Seed 7 的一轮实验包含 30 个任务,持续 10.6 分钟,出现 25 次授权请求,累计等待 144 s。模拟用户在提示出现后 3 到 8 s 回答,在任务结束后 3 到 10 s 输入下一项任务。
静默计时器遇到了两种冲突:
- 阈值低于 5 s,会把静默工具调用当成等待用户;阈值不超过 3 s 时,30 次任务结束也全部触发。
- 阈值达到 5 s 后,又开始漏掉及时回答的提示;8 s 阈值漏掉全部 25 次,是因为模拟用户最多等待 8 s。
行尾问号也不可靠。它在真实 Fixture 中恰好只命中四份 blocked 录制,但动态实验里,Agent 自己提出的回归测试问题会留在屏幕上,延续到下一项任务。
清理后的 8 KB 尾部在模拟实验中能识别提示,因为模拟器给 ❯ 1. Yes 写了真实空格;然而回答后,它平均还会误报 77 s。提示虽然从屏幕擦掉,旧字节仍留在尾部,要等新输出挤出去。Spinner 每秒约 600 Bytes,再夹杂静默阶段,这个过程并不快。
700 ms 屏幕轮询的延迟则符合预期:中位数 0.26 s,最大 0.70 s,回答后的状态消退也最多落后一个周期。
进程状态同样没能区分等待原因。作者每秒两次执行 ps -o stat=,time=:working 的 603 个样本中,99.5% 为 S、0.5% 为 R;idle 的 357 个、blocked 的 280 个样本全部为 S。三者 CPU 占单核比例分别是 0.04%、0.02%、0.02%,wchan 都显示 -。
这些数据来自模拟进程,但至少说明:睡眠状态无法区分等输入和等下一帧。对于 Node CLI,事件循环也不能直接告诉你业务上在等谁。
能让 Agent 主动报告,为什么还要保留读屏
osFoundry 自己启动 Claude Code 时,通过 --settings 注入 Hooks。原文说明,这只补充本次启动的设置,用户原有 Hooks 继续执行。
UserPromptSubmit 和 PreToolUse 调用 osf-pane report working,后者使用 matcher: "*";Stop 调用 osf-pane report done;Notification 调用 osf-pane notify。命令都带有 >/dev/null 2>&1 || true。
Notification 既可能来自授权请求,也可能来自等待输入满一分钟,所以不能一律当作 blocked。osf-pane notify 读取 Payload,把 permission 映射为 blocked,其他情况映射为 idle。通过 claude-agent-acp 接入 ACP 时,session/request_permission 则是明确的授权请求。
signals::combine 的优先级是:屏幕 blocked、最新 Agent Report、Shell 已回到前台、其他屏幕规则。作者的理由是,上报可能过期或因 Hook 静默失败而缺失,明确的屏幕授权提示不能被它压住。
进程表负责补充身份信息。MasterPty::process_group_leader 使用 tcgetpgrp 读取终端前台进程组,可以识别用户从普通 Shell 启动的 claude,也能发现 Agent 退出后 Shell 已重新接管终端。
通知按 Session 和状态设置 30 s 冷却,避免重绘导致 blocked → working → blocked 反复报警。真正向 Pane 输入前,还会重新执行 classify_session;blocked 状态只允许用户确认的 runtime_answer,不接受普通 runtime_send。
我的判断
最有说服力的是「还原当前屏幕」这一层。真实录制已经证明,删除 ANSI 转义序列不等于恢复终端文字;旧提示残留在字节尾部,也确实会破坏状态判断。
但 11/11 只覆盖 11 份录制。原文没有给出不同终端尺寸、更多版本或不同界面语言的验证,动态实验也只有所展示的 Seed 7 一轮。延迟和误报数字适合解释机制,不能直接作为生产准确率承诺。
「屏幕 blocked 永远优先」同样有前提:匹配必须足够具体。若普通回复里恰好出现了规则文字,错误读屏也可能压过正确上报。原文没有给出这类误报的覆盖数据。
我赞成优先使用 Hooks 或 ACP,再用屏幕补足缺失信息。不过「误报比晚报更贵」属于产品取舍,700 ms 适合作者描述的人工查看徽标;自动输入的动作则需要像他们那样,在执行前重新判断。
能拿来做什么
- 如果你在做 AI 编程终端或 Session 看板,先接入终端解析器,再对当前屏幕写规则,并保存真实原始录制用于回放验证。
- 如果你维护 Node CLI,尽量提供明确的状态事件或协议,让外部工具能区分工作、空闲和授权等待,减少对展示文案的依赖。
- 如果你已有向终端自动输入的工具,应在发送前重新分类,并为授权回答保留独立入口,避免普通任务输入落进权限菜单。
- 如果你只能观察终端,把静默时间和进程状态当作辅助信息;通知识别当前界面的具体提示,并对重复提醒设置冷却。