🧠

Claude Codeとの会話を自動でObsidianに記録する仕組みを作った

に公開

はじめに

Claude Code(Anthropic公式のCLIツール)を使って日々の作業をしていると、有益な会話がセッション終了とともに消えてしまうのがもったいないと感じていました。

そこで、Claude Codeとの会話を自動的にObsidianに記録する仕組みを作りました。本記事ではその実装方法を紹介します。

やりたいこと

  • Claude Codeでの会話を自動的にMarkdownファイルとして保存
  • Obsidianで管理しているナレッジベースに統合
  • 手動操作なしで、会話のたびにリアルタイム同期
  • ノイズ(システムメッセージなど)を除去してクリーンな記録を残す

仕組みの概要

Claude Code セッション
    ↓ (jsonlファイルに記録)
~/.claude/projects/*/session.jsonl
    ↓ (5秒ごとに監視)
watch-and-save.sh (LaunchAgentで常駐)
    ↓ (変更検知時に抽出・整形)
~/obsidian/claude/YYYY年M月D日.md
    ↓ (自動git commit)
GitHub同期

実装

1. 監視スクリプトの作成

~/.claude/hooks/watch-and-save.sh:

#!/bin/bash
# Watch Claude Code session and sync to Obsidian in real-time (append mode)

OBSIDIAN_DIR="$HOME/src/github.com/kentaro/obsidian/claude"
SESSION_DIR="$HOME/.claude/projects/-Users-antipop-tmp-claude"  # 監視するプロジェクトディレクトリ
LAST_LINE_FILE="$HOME/.claude/last-synced-line"

mkdir -p "$OBSIDIAN_DIR"

sync_session() {
    local session_file="$1"
    local TODAY=$(date +%Y年%-m月%-d日)
    local TODAY_START_UTC=$(TZ=UTC date -v-9H -j -f "%Y-%m-%d %H:%M:%S" "$(date +%Y-%m-%d) 00:00:00" +%Y-%m-%dT%H:%M:%S 2>/dev/null)
    local OUTPUT_FILE="${OBSIDIAN_DIR}/${TODAY}.md"

    # Create file with header if it doesn't exist
    if [ ! -f "$OUTPUT_FILE" ]; then
        echo "# ${TODAY} Claudeとの会話" > "$OUTPUT_FILE"
        echo "" >> "$OUTPUT_FILE"
    fi

    # Get last synced line number
    local last_line=0
    if [ -f "$LAST_LINE_FILE" ]; then
        last_line=$(cat "$LAST_LINE_FILE" 2>/dev/null || echo 0)
    fi

    # Count current lines in session file
    local current_lines=$(wc -l < "$session_file" | tr -d ' ')

    # Only process new lines (append mode - no overwriting)
    if [ "$current_lines" -gt "$last_line" ]; then
        local new_content=$(tail -n +$((last_line + 1)) "$session_file" | jq -r --arg today_start "$TODAY_START_UTC" '
        select(.type == "user" or .type == "assistant") |
        select((.timestamp // "9999") >= $today_start) |
        if .type == "user" then
            (.message.content // .content // "") as $content |
            if ($content | type) == "string" then
                if ($content | test("<local-command|<command-name>|<system-reminder>|<task-notification>"; "i")) then
                    empty
                else
                    "**ユーザー**: " + $content
                end
            else
                empty
            end
        elif .type == "assistant" then
            if (.message.content | type) == "array" then
                (.message.content[] | select(.type == "text") |
                    if (.text | test("^No response requested"; "i")) then
                        empty
                    else
                        "**Claude**: " + .text
                    end
                )
            else
                empty
            end
        else
            empty
        end
        ' 2>/dev/null)

        # Append new content if any
        if [ -n "$new_content" ]; then
            echo "$new_content" >> "$OUTPUT_FILE"
            echo "" >> "$OUTPUT_FILE"

            # Git commit (silent)
            cd "$OBSIDIAN_DIR" && git add "$OUTPUT_FILE" && \
                git commit -m "Claude: ${TODAY} (auto)" 2>/dev/null || true
        fi

        # Update last synced line
        echo "$current_lines" > "$LAST_LINE_FILE"
    fi
}

# Find most recent active session (exclude subagents)
find_session() {
    find "$SESSION_DIR" -path "*/subagents/*" -prune -o \
        -name "*.jsonl" -type f -mmin -60 -size +1000c -print \
        2>/dev/null | xargs ls -t 2>/dev/null | head -1
}

echo "Watching for Claude session changes (append mode)..."

while true; do
    SESSION=$(find_session)
    if [ -n "$SESSION" ]; then
        sync_session "$SESSION"
    fi
    sleep 5
done

2. LaunchAgentで常駐化

~/Library/LaunchAgents/com.claude.obsidian-sync.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.claude.obsidian-sync</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>~/.claude/hooks/watch-and-save.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>~/.claude/logs/obsidian-sync.log</string>
    <key>StandardErrorPath</key>
    <string>~/.claude/logs/obsidian-sync-error.log</string>
</dict>
</plist>

3. LaunchAgentの有効化

# ログディレクトリ作成
mkdir -p ~/.claude/logs

# LaunchAgentを読み込み
launchctl load ~/Library/LaunchAgents/com.claude.obsidian-sync.plist

# 動作確認
launchctl list | grep claude

4. chezmoiで管理(オプション)

複数のMacで同じ設定を使う場合は、chezmoiでdotfiles管理すると便利です。

chezmoi add ~/.claude/hooks/watch-and-save.sh
chezmoi add ~/.claude/settings.json
# LaunchAgentはテンプレート化して追加

出力例

2026年1月9日.md:

# 2026年1月9日 Claudeとの会話

**ユーザー**: uvを入れて
**Claude**: uvをインストールします。
**Claude**: uv 0.9.22 がインストールされました。

**ユーザー**: brewで入れるんじゃないの?
**Claude**: そうですね、macOSならHomebrewの方が管理しやすいです。入れ直します。
...

ポイント

セッションファイルの場所

Claude Codeは会話履歴を ~/.claude/projects/{project-hash}/{session-id}.jsonl に保存しています。このjsonlファイルをパースすることで、会話内容を抽出できます。

ノイズの除去

Claude Codeの内部メッセージ(<system-reminder>など)や、ローカルコマンドの出力(<local-command-...>)は記録から除外しています。

追記モード

上書きではなく追記モードを採用しています。セッションファイルの新しい行だけを処理し、既存のObsidianファイルに追記します。これにより:

  • 他のプロセス(カメラ監視など)との競合を防止
  • セッション途中でクラッシュしても記録が失われない
  • 5秒ごとに新しい会話だけを追記

日付ベースのフィルタリング

セッションファイルには日をまたいだ会話も含まれるため、各エントリのタイムスタンプを確認し、その日の会話だけを抽出しています。これにより、2026年1月9日.mdには1月9日の会話のみ、2026年1月10日.mdには1月10日の会話のみが記録されます。

今後の展望

  • トピックごとの自動分類
  • 要約の自動生成
  • スマホのClaudeアプリからの会話もGitHub API経由で統合

まとめ

Claude Codeとの会話を自動的にObsidianに記録する仕組みを作りました。これにより:

  • 過去の会話をいつでも検索・参照できる
  • 学んだことがナレッジベースに蓄積される
  • 手動操作なしで自動的に記録される

Claude Codeを日常的に使っている方は、ぜひ試してみてください。

GitHubで編集を提案
GMOペパボ株式会社

Discussion