Claude Code + Obsidian 日志自动记录配置指南

概述

通过 Claude Code 的 Hook 机制,每次对话自动将内容写入 Obsidian Vault,无需手动复制粘贴。支持两种触发方式:

  • 实时记录:用户发送消息时立即写入一条记录
  • 完整记录:对话结束时读取 transcript,写入完整的对话轮次(含用户提问 + Claude 回复)

最终效果

D:\dong_valut\dong_valut\sources\
├── 2026-05-21-上午.md     # 上午的对话记录
├── 2026-05-21-下午.md     # 下午的对话记录
└── 2026-05-21-晚上.md     # 晚上的对话记录

文件内容示例:

> **用户**: 帮我检查一下这个页面的布局

---

> **用户**: 还有一个问题想问

---

## 对话记录 - 2026-05-21

> **用户**: 帮我检查一下这个页面的布局

> **Claude**: 好的,我来检查这个页面的布局代码...

> **用户**: 还有一个问题想问

> **Claude**: 请说,还有什么问题?

> 块引用格式是 Obsidian 最通用的格式,兼容所有主题和插件。


前置准备

安装 Obsidian

  1. 访问 obsidian.md 下载安装包
  2. 安装后启动 Obsidian,创建一个 Vault(知识库),例如命名为 dong_valut
  3. 在 Vault 内创建 sources 目录用于存放日志文件(路径如 D:\dong_valut\dong_valut\sources\
  4. (可选)安装 Obsidian 官方 CLI:Obsidian 安装后自带 obsidian 命令,通常位于 C:\Program Files\Obsidian\Obsidian.exe

使用 Obsidian CLI

Obsidian 支持命令行打开指定 Vault,配合 Claude Code 可以随时跳转查看日志:

# 打开默认 Vault
obsidian

# 打开指定 Vault
obsidian "vault=dong_valut"

# 在指定 Vault 中打开当天的日志文件
obsidian "vault=dong_valut" daily

# 通过 Windows 路径直接打开
"C:\Program Files\Obsidian\Obsidian.exe" "vault=dong_valut"

如果只有一个 Vault,obsidian 命令会自动打开它,无需指定 vault= 参数。


配置步骤

第一步:创建 hooks 目录

C:\Users\<你的用户名>\.claude\ 下创建 hooks\ 文件夹:

mkdir "C:\Users\<你的用户名>\.claude\hooks"

第二步:创建 Python 脚本

新建文件 C:\Users\<你的用户名>\.claude\hooks\obsidian-log.py,内容如下:

import sys
import json
from datetime import datetime
from pathlib import Path

# ============================================================
# 修改此处为你的 Obsidian Vault 目标目录
# ============================================================
VAULT_DIR = Path(r"D:\dong_valut\dong_valut\sources")
VAULT_DIR.mkdir(parents=True, exist_ok=True)

raw_bytes = sys.stdin.buffer.read()
raw = raw_bytes.decode("utf-8")
if not raw.strip():
    sys.exit(0)

try:
    data = json.loads(raw)
except json.JSONDecodeError:
    sys.exit(0)

event = data.get("hook_event_name", "")
now = datetime.now()
today = now.strftime("%Y-%m-%d")
hour = now.hour
if hour < 12:
    period = "上午"
elif hour < 19:
    period = "下午"
else:
    period = "晚上"
file_path = VAULT_DIR / f"{today}-{period}.md"

# ── UserPromptSubmit: 用户发送消息时立即记录 ──
if event == "UserPromptSubmit":
    prompt = data.get("prompt", "").strip()
    if prompt:
        with open(file_path, "a", encoding="utf-8") as f:
            f.write(f"\n> **用户**: {prompt}\n\n---\n")
    sys.exit(0)

# ── Stop: 对话结束时记录完整 transcript ──
if event == "Stop":
    if data.get("stop_hook_active", False):
        sys.exit(0)

    transcript_path = data.get("transcript_path", "")
    if not transcript_path or not Path(transcript_path).exists():
        sys.exit(0)

    with open(transcript_path, "r", encoding="utf-8") as f:
        lines = f.readlines()

    turns = []
    for line in lines:
        try:
            entry = json.loads(line)
        except json.JSONDecodeError:
            continue
        if entry.get("type") not in ("user", "assistant"):
            continue
        msg = entry.get("message", {})
        if not isinstance(msg, dict):
            continue
        role = msg.get("role", "")
        content = msg.get("content", [])

        texts = []

        # content 是字符串(用户直接输入的文本)
        if isinstance(content, str):
            t = content.strip()
            if len(t) > 5:
                texts.append(t)

        # content 是数组(assistant 回复中的 text blocks)
        elif isinstance(content, list):
            for block in content:
                if not isinstance(block, dict):
                    continue
                bt = block.get("type", "")
                if bt == "text":
                    t = block.get("text", "").strip()
                    if len(t) > 5:
                        texts.append(t)

        if texts:
            turns.append((role, "\n\n".join(texts)))

    if not turns:
        sys.exit(0)

    with open(file_path, "a", encoding="utf-8") as f:
        f.write(f"\n## 对话记录 - {today}\n\n")
        for role, text in turns:
            prefix = "**用户**: " if role == "user" else "**Claude**: "
            f.write(f"> {prefix}{text}\n\n")

sys.exit(0)

第三步:创建批处理文件(Windows 入口)

新建文件 C:\Users\<你的用户名>\.claude\hooks\obsidian-log.bat

@echo off
python C:\Users\<你的用户名>\.claude\hooks\obsidian-log.py
exit /b 0

第四步:配置 Claude Code Hook

编辑 C:\Users\<你的用户名>\.claude\settings.json,在 hooks 字段中添加:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "C:\\Users\\<你的用户名>\\.claude\\hooks\\obsidian-log.bat",
            "timeout": 10
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "C:\\Users\\<你的用户名>\\.claude\\hooks\\obsidian-log.bat",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

如果已有其他 hook 配置(如 PreToolUse / PostToolUse),保留它们,直接追加上述片段到 hooks 对象中。


关键原理

Hook 事件

事件 触发时机 脚本操作
UserPromptSubmit 用户按 Enter 发送消息时 立即将用户消息写入当天的 .md 文件
Stop 对话结束(/exit、关闭终端等) 读取 transcript 文件,提取完整对话写入

日志格式

  • 文件名:YYYY-MM-DD-上午.md / YYYY-MM-DD-下午.md / YYYY-MM-DD-晚上.md
  • 时间分段:上午(0-11点) / 下午(12-18点) / 晚上(19-23点)
  • 内容使用 > blockquote 格式,兼容 Obsidian

重要注意事项

  1. UTF-8 编码:必须使用 sys.stdin.buffer.read().decode("utf-8"),直接 sys.stdin.read() 在 Windows 上会导致中文乱码
  2. 字符串 content:用户消息的 content 字段是字符串格式(不是数组),脚本需要同时处理 strlist 两种情况
  3. 防止自循环:Stop hook 内部会检查 stop_hook_active 标志,避免脚本自身触发 Bash 命令导致递归
  4. Python 依赖:脚本只依赖 Python 标准库,无需额外安装包

自定义修改

修改 Vault 目录

在 Python 脚本中修改第 8 行的 VAULT_DIR

VAULT_DIR = Path(r"D:\你的路径\你的Vault\sources")

修改文件名分段规则

修改时间分段逻辑(第 23-28 行),例如改为上/下午两段:

if hour < 12:
    period = "上午"
else:
    period = "下午"

只保留一种记录方式

如果不需要实时记录,删除 UserPromptSubmit 事件处理块(第 32-37 行);
如果不需要完整记录,删除 Stop 事件处理块(第 40-94 行)。


验证步骤

  1. 发送一条消息,检查 vault 目录下是否生成了对应的 .md 文件,内容包含 > **用户**: 你的消息
  2. 结束对话(输入 /exit),检查 vault 文件末尾是否追加了 ## 对话记录 及完整对话内容
  3. 如遇问题,可在脚本开头添加调试日志排障:
with open(r"D:\你的路径\sources\_debug.log", "a", encoding="utf-8") as d:
    d.write(f"event={event}, len={len(raw)}\n")

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐