Claude Subconscious退出码规范解读:0、1、2三种语义如何决定Hook是否阻塞
【免费下载链接】claude-subconsciousGive Claude Code a subconscious项目地址: https://gitcode.com/GitHub_Trending/cl/claude-subconscious
Claude Subconscious 是一个给 Claude Code 装"潜意识"的背景插件:它在后台观察你的会话、读取代码、积累记忆,并在你下次提问前"耳语"提示。整个插件完全靠 4 个 Hook 脚本驱动,而这些脚本正是用0、1、2 三种退出码(Exit Code)语义来告诉 Claude Code:"成功了"、"失败了但别管我",还是"停下来,这次操作不能继续"。读懂这套退出码规范,你就理解了它"永不出错、永不阻塞"的设计精髓。
Hook 退出码规范:Claude Code 的三档"交通灯"
在 Claude Code 的 Hook 机制中,脚本的退出码就是与主流程对话的唯一信号,语义如下:
| 退出码 | 语义 | Claude Code 的行为 |
|---|---|---|
| 0 | 成功(或无需操作) | 一切正常,继续执行;stdout 内容可被注入上下文 |
| 1 | 非阻塞错误(Non-blocking error) | 错误只记录到 stderr,不中断当前操作 |
| 2 | 阻塞错误(Blocking error) | 阻止当前操作继续(如阻断工具调用、拦截 prompt 处理) |
本项目的 4 个 Hook 在 hooks/hooks.json 中统一注册,并各自配置了超时(5s / 10s / 120s),确保任何脚本都不会无限卡住主流程:
| Hook 事件 | 脚本 | 超时 | 实际使用的退出码 |
|---|---|---|---|
SessionStart | session_start.ts | 5s | 0、1 |
UserPromptSubmit | sync_letta_memory.ts | 10s | 0、1(2 保留) |
PreToolUse | pretool_sync.ts | 5s | 0、1 |
Stop | send_messages_to_letta.ts | 120s(异步) | 0、1 |
每个脚本的文件头部注释都明确写了自己的退出码契约,例如 sync_letta_memory.ts 开篇即声明:
0 - Success / 1 - Non-blocking error (logged to stderr) / 2 - Blocking error (prevents prompt processing)
0 号语义:成功,也包括"无事发生的沉默"
process.exit(0)在这个项目里出现频率最高,它覆盖两类场景:
- 真正成功:记忆同步完成、会话通知发出、后台 worker 已启动。
- 合法的"空操作":比如
LETTA_MODE=off时直接静默退出;pretool_sync.ts 在检查发现"没有新消息、没有记忆变更"时也返回 0——对高频触发的PreToolUse钩子来说,沉默就是正确的回答。
这里有个值得学习的细节:pretool_sync.ts 的catch分支捕获异常后依然返回 0,注释写着 "Non-blocking - just exit silently"。即使出错,它也选择不打扰你——这与 1 号语义形成了鲜明对比。
1 号语义:非阻塞错误,"我失败了,但你可以继续"
当脚本遇到LETTA_API_KEY未设置、网络请求失败等错误时,会process.exit(1)并把错误信息写入 stderr。按 Claude Code 的规范,退出码 1只会在界面上记录一条钩子错误,绝不会中断你的会话或工具调用。
典型代码见 sync_letta_memory.ts 的catch块:
} catch (error) { console.error(`Error syncing Letta memory: ${errorMessage}`); // Exit with code 1 for non-blocking error // Change to exit(2) if you want to block prompt processing on sync failures process.exit(1); }注意这行关键注释:"如果你想让同步失败时阻断 prompt 处理,就把这里改成 exit(2)"——作者把 1 和 2 的抉择权,直接留在了源码里。
2 号语义:阻塞错误,项目保留但默认不启用
退出码 2 在 Claude Code 中的力量是"一票否决":PreToolUse钩子返回 2 会阻止该次工具执行,UserPromptSubmit返回 2 则拦截整条 prompt。
但在 Claude Subconscious 的设计哲学中(README 称之为"Never blocks"),默认所有错误都走 1 号通道。原因很直白:
- 它只是个"背景意识",后台服务抖动不该绑架你正在写的前台代码;
- 配合 5s~10s 的短超时,最坏情况也只是多等几秒;
- 真正耗时的大任务(
Stop钩子发送完整转录)被设为async: true异步执行,见 hooks/hooks.json,根本不占用退出码通道。
也就是说:0 和 1 是日常用语,2 是保险丝——文档化的保留开关,供想强制"记忆同步失败就停止"的用户自行改造使用。
🧭 新手调试清单:如何验证 Hook 是否被阻塞
- 看日志:所有钩子的运行记录都在
$TMPDIR/letta-claude-sync-$UID/下,如session_start.log、send_messages.log; - 开调试:设置
LETTA_DEBUG=1,脚本会把细节写到 stderr(不影响退出码); - 查退出码:若界面上频繁出现钩子报错,多半是退出码 1 的非阻塞错误,优先检查
LETTA_API_KEY是否设置; - 改阻塞行为:若你确实希望某个失败"卡住"流程,只需把对应脚本的
process.exit(1)改为process.exit(2),这正是 sync_letta_memory.ts 注释指出的路径。
总结
Claude Subconscious 用一套极简的退出码契约实现了"背景代理"的可靠边界:
- 0 = 继续(含正常沉默);
- 1 = 报错但不打扰;
- 2 = 阻断(默认不启用,留给进阶用户)。
理解这 3 个数字,你就理解了所有 Claude Code 插件开发中最核心的接口约定:退出码即策略,语义即行为。想动手研究,可通读 README.md 的 Hooks 章节与 scripts/ 目录下的 4 个钩子脚本源码。
【免费下载链接】claude-subconsciousGive Claude Code a subconscious项目地址: https://gitcode.com/GitHub_Trending/cl/claude-subconscious
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考