Claude Subconscious防止无限循环机制解读:stop_hook_active设计全解
【免费下载链接】claude-subconsciousGive Claude Code a subconscious项目地址: https://gitcode.com/GitHub_Trending/cl/claude-subconscious
Claude Subconscious是一个为 Claude Code 提供"潜意识"的开源插件:一个后台智能体默默观察你的每次会话,跨会话积累记忆,并在你输入下一条提示前悄悄"耳语"回指引。而它的 Stop 钩子每次响应后都会被触发,一旦设计不当极易陷入无限循环——本文带你完整解读stop_hook_active这个 3 行守卫如何优雅防死循环。
什么是 Claude Subconscious?
一句话概括:Give Claude Code a subconscious(给 Claude Code 装个潜意识)。
它的核心工作流是:
| 时机 | 动作 | 效果 |
|---|---|---|
| 每次响应后 | Stop 钩子异步发送会话记录到后台智能体 | 智能体读代码、更新记忆,不阻塞主流程 |
| 每次输入前 | UserPromptSubmit 钩子注入记忆与消息 | Claude 提前拿到跨会话上下文 |
| 每次工具调用前 | PreToolUse 钩子注入增量更新 | 长任务中保持上下文不漂移 |
其中Stop 钩子是防循环设计的重灾区,我们重点来看它。
为什么 Stop 钩子会无限循环?
先理解 Claude Code 的钩子机制:当 Claude 结束一轮回答时,会触发Stop钩子。如果钩子以阻塞式退出码(exit code 2)结束并返回反馈,Claude Code 会认为"还不能停",让 Claude 继续工作——然后再次触发 Stop 钩子。
如果钩子脚本没有意识到"我是被二次触发的",就会出现:
Claude 停止 → 触发钩子 → 钩子阻塞退出 → Claude 继续 → 又停止 → 又触发钩子 → ……(死循环)对于 Claude Subconscious 来说,每次循环都意味着重复发送整段会话记录给后台智能体,既浪费 token 又污染记忆。
stop_hook_active:Claude Code 的内置安全开关
Claude Code 为此在钩子输入中内置了一个布尔信号:stop_hook_active。
它表示"上一次 Stop 钩子已经激活过"——是平台层送给钩子脚本的一面"别再阻塞"的小旗子。
在 scripts/send_messages_to_letta.ts 中,钩子输入被完整建模:
interface HookInput { session_id: string; transcript_path: string; stop_hook_active?: boolean; // ← Stop 钩子是否已处于激活状态 cwd: string; hook_event_name?: string; }3 行代码的防循环守卫(源码全解)
整个防护逻辑浓缩在 scripts/send_messages_to_letta.ts 的第 142-146 行:
// Prevent infinite loops if stop hook is already active if (hookInput.stop_hook_active) { log('Stop hook already active, exiting to prevent loop'); process.exit(0); }设计上有 3 个讲究:
- 🛡️ 提前短路:守卫放在读取会话记录、调用 API 等一切重操作之前,二次触发时零开销退出
- 📝 留痕可查:退出前写入一条日志,方便你在
$TMPDIR/letta-claude-sync-$UID/send_messages.log中确认守卫确实生效过 - 🟢 以 0 退出:用成功码而非错误码退出,避免触发任何告警或重试逻辑——"我没事,只是不该再做了"
双保险:异步钩子 + 快速退出设计
stop_hook_active守卫只是第一道防线,Claude Subconscious 实际上有三层防循环设计:
第一层:async 异步钩子。在 hooks/hooks.json 中,Stop 钩子注册了"async": true和 120 秒超时——它永远在后台运行,天然不会阻塞 Claude 的停止流程,从源头消除了"阻塞→重触发"的循环条件。
第二层:stop_hook_active 守卫。即使某次运行处于激活状态,脚本也会立即静默退出(见上文源码)。
第三层:快进快出 + 后台 Worker。主钩子只做轻量工作:解析 JSONL 会话记录、把 payload 写入临时文件、派生一个 send_worker_sdk.ts 分离后台进程后立即退出(详见 README.md 的 Stop 章节)。主钩子越快退出,与 Claude Code 生命周期重叠的窗口就越小。
主钩子(秒级):解析记录 → 写 payload → 派生 worker → 退出 后台 worker(独立):恢复 Letta 会话 → 智能体处理 → 更新状态 → 清理临时文件如何验证防循环机制生效?
运行插件后,用一行命令观察日志即可:
tail -f /tmp/letta-claude-sync-$(id -u)/send_messages.log每次 Stop 触发,日志都会打印当前信号值(send_messages_to_letta.ts 第 138 行):
stop_hook_active: false ← 正常触发,继续发送会话 stop_hook_active: true ← 若出现,下一行就是 "exiting to prevent loop"正常情况下你只会看到false;一旦看到true后紧跟退出日志,说明守卫按预期拦截了二次触发,而非真的出现了异常循环。
相关文件速查
| 文件 | 职责 |
|---|---|
| hooks/hooks.json | Stop 钩子注册(async + 120s 超时) |
| scripts/send_messages_to_letta.ts | 主钩子:stop_hook_active 防循环守卫 |
| scripts/send_worker_sdk.ts | 后台 Worker:独立进程完成会话投递 |
| README.md | Stop 钩子异步模式的官方说明 |
| CHANGELOG.md | 版本演进记录 |
想要动手体验?克隆仓库后运行npm install,再在 Claude Code 中启用插件即可:
git clone https://gitcode.com/GitHub_Trending/cl/claude-subconscious cd claude-subconscious && npm install小结
stop_hook_active防循环机制的精髓在于**"平台给信号,脚本听劝,异步兜底"**:3 行守卫代码成本极低,却与 async 钩子、快进快出设计共同构成纵深防御。这也是所有 Claude Code 钩子开发者值得抄的作业——任何可能阻塞会话的钩子,都应先检查再行动。
【免费下载链接】claude-subconsciousGive Claude Code a subconscious项目地址: https://gitcode.com/GitHub_Trending/cl/claude-subconscious
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考