Skip to content

4. Hook 方案详解

入口:langfuse_trace/common/langfuse_transcript.pymain()
部署:langfuse_trace/hook/langfuse_hook_all_agents.py(薄封装)


1. 工作原理

Hook vs Watcher 方案对比

你在 Claude Code 里发消息、AI 回复

Claude Code 写入 session jsonl

本轮结束(Stop 事件)→ 触发 Hook 脚本

Hook 从 stdin 读取 {sessionId, transcriptPath}

read_new_jsonl() 增量读取新增行

build_turns() → emit_main_turn() → flush()

Langfuse UI 出现新 Trace

生活类比:每做完一道菜,厨师按一下铃,会计过来把刚才的流水账录入系统。


2. 触发时机

Hook 注册在 Claude Code 的 Stop 事件上。每次 AI 完成一轮回复(stop_reason=end_turn),Claude Code 会:

  1. 把 Hook 脚本路径和 payload 写入 stdin
  2. 等待 Hook 退出(必须快速返回,不能阻塞)

payload 结构

json
{
  "sessionId": "b0dcb60c-5539-42a7-b71b-80ffc878ec0f",
  "transcriptPath": "/home/user/.claude/projects/abc123/b0dcb60c....jsonl"
}

子代理 Hook 跳过

python
if transcript_path.name.startswith("agent-"):
    return 0  # 子代理由主 Hook 内联处理,不单独上报

3. main() 执行流程

python
def main() -> int:
    # ① 开关检查
    if os.environ.get("TRACE_TO_LANGFUSE") != "true":
        return 0

    # ② 凭证检查(缺失则静默退出)
    if not public_key or not secret_key:
        return 0

    # ③ 读 Hook payload
    payload = read_hook_payload()
    session_id, transcript_path = extract_session_and_transcript(payload)

    # ④ 初始化 Langfuse + SDK 版本检查
    langfuse = Langfuse(public_key=..., secret_key=..., host=...)

    # ⑤ 加锁读状态 → 增量读 jsonl → 切 Turn
    with FileLock(LOCK_FILE):
        state = load_state()
        ss = load_session_state(state, key)
        new_msgs, ss_after = read_new_jsonl(transcript_path, ss)
        turns = build_turns(new_msgs)

        # ⑥ 逐 Turn 上报
        for t in turns:
            emit_main_turn(langfuse, session_id, turn_num, t, ...)
            emitted_ok += 1

        # ⑦ flush 成功才提交进度
        if flush_ok and emitted_ok == len(turns):
            ss_after.turn_count += emitted_ok
            write_session_state(state, key, ss_after)
        save_state(state)

    return 0  # 永远返回 0

4. 状态管理

文件用途
~/.claude/state/langfuse_hook_all_agents_state.json全局状态(按 session+path 分 key)
~/.claude/state/langfuse_hook_all_agents_state.lock文件锁,防并发 Hook
~/.claude/state/langfuse_hook.log运行日志

每个 session 的状态:

json
{
  "a1b2c3...sha256": {
    "offset": 45678,
    "buffer": "",
    "turn_count": 5,
    "updated": "2026-06-05T10:00:00+00:00"
  }
}

可靠投递

上报 3 个 Turn,第 2 个失败 或 flush 失败
  → offset/turn_count 不推进
  → 下次 Stop 重读同一批新行
  → 从失败的 Turn 重试

5. 安装与配置

5.1 安装依赖

bash
cd langfuse_trace
pip install -r requirements.txt   # langfuse>=2,<3

5.2 部署 Hook 脚本

bash
cp hook/langfuse_hook_all_agents.py ~/.claude/hooks/

5.3 环境变量

bash
export TRACE_TO_LANGFUSE=true
export CC_LANGFUSE_PUBLIC_KEY=pk-lf-xxx
export CC_LANGFUSE_SECRET_KEY=sk-lf-xxx
# 可选
export CC_LANGFUSE_BASE_URL=https://cloud.langfuse.com
export CC_LANGFUSE_DEBUG=true

5.4 注册 Hook

~/.claude/settings.jsonhooks.Stop 中注册脚本路径。详见 langfuse_trace/方案实现说明.md


6. Hook 方案的特点

优点缺点
与 Claude Code 生命周期绑定需改 settings.json
每轮结束才触发,逻辑简单非流式,Turn 结束前 UI 看不到
fail-open,不影响 CCStop 时若 Langfuse 不可达,当轮延迟上报
共享 common 模块,易维护与 Watcher 不可同时开

7. Hook vs Watcher 在 emit 上的差异

Hook 的 main() 不走流式模式,每次 Stop 触发时:

python
turns = build_turns(new_msgs)          # 只处理新增消息
for t in turns:
    emit_main_turn(...)                # 批量一次性上报完整 Turn

Watcher 默认走 process_turn_streaming()(见 6_streaming_mode.md)。

Hook 也可以启用流式——但目前 main() 未调用 process_turn_streaming(),这是两者最大的行为差异。


8. 调试

bash
# 开启详细日志
export CC_LANGFUSE_DEBUG=true

# 查看日志
tail -f ~/.claude/state/langfuse_hook.log

# 端到端测试
cd langfuse_trace/hook
export CC_LANGFUSE_PUBLIC_KEY=pk-lf-xxx
export CC_LANGFUSE_SECRET_KEY=sk-lf-xxx
./run_e2e_langfuse_test.sh

下一章:5_watcher_scheme.md