Claude Codeで開発を進めると、セッション毎に貴重なやり取りが蓄積されていきます。しかし、そのログファイル(JSONL形式)は人間が読める形式ではありません。この記事では、以前紹介したClaude Chat Exporterを大幅に拡張し、Claude Codeのセッションログを一括でMarkdown化できる神機能を実装したツールを紹介します。
なぜClaude Codeのセッションログを管理するのか?
Claude Codeでの開発セッションには、通常のチャット以上の価値があります:
- 実装の全プロセス: 設計から実装、テスト、デバッグまでの完全な記録
- コンテキスト付きの会話: ファイル編集、コマンド実行の履歴と結果
- 問題解決の軌跡: エラーとその解決方法の詳細な記録
- アーキテクチャ判断: 技術選定や設計判断の理由
- ベストプラクティス: Claude Codeとの協働で得られた知見
これらを適切に管理することで:
- ✅ プロジェクトの開発履歴として参照できる
- ✅ 同じ問題に遭遇した時の解決策を即座に見つけられる
- ✅ チーム内でClaude Code活用のノウハウを共有できる
- ✅ 技術ブログやドキュメントの素材になる
- ✅ 開発プロセスの振り返りと改善に活用できる
以前のツールとの違い
以前の記事で紹介したClaude Chat Exporterは、claude.aiのWebチャット用でした。今回の拡張版では、以下の新機能を追加しています:
新機能一覧
| 機能 | 旧バージョン | 新バージョン |
|---|---|---|
| 対応形式 | JSON(conversations.json)のみ | JSON + JSONL(Claude Code) |
| ファイル処理 | 単一ファイル | 複数ファイル一括処理 |
| 入力方法 | ダイアログのみ | ダイアログ + ドラッグ&ドロップ + コマンドライン |
| セッション識別 | チャットタイトルのみ | セッションID + フォルダ名 |
| メッセージフィルタ | なし | システムメッセージ、ツール結果を自動除外 |
| コンテンツ処理 | シンプルテキスト | 配列形式、複雑な構造に対応 |
Claude CodeのJSONL形式とは?
Claude Codeは各セッションのログを .jsonl(JSON Lines)形式で保存します:
C:\Users\<ユーザー名>\.claude\sessions\<セッションID>\transcript.jsonl各行が独立したJSONオブジェクトで、以下のような情報が含まれます:
{"type":"user","sessionId":"abc123...","message":{"role":"user","content":"関数を作成して"}}
{"type":"assistant","sessionId":"abc123...","message":{"role":"assistant","content":"承知しました..."}}この形式は機械的な処理には適していますが、人間が読むには不向きです。
ツールの全機能
1. 複数ファイル一括処理
exporter.py にファイルを複数ドロップ → 一括処理
# コマンドライン
python exporter.py file1.jsonl file2.json file3.jsonl
# ダイアログ
python exporter.py → 複数ファイル選択可能2. JSON/JSONL両対応
- JSON: claude.aiのWebチャットエクスポート(conversations.json)
- JSONL: Claude Codeのセッションログ(transcript.jsonl)
3. 賢いフィルタリング
自動で不要なメッセージを除外:
- システムメッセージ(
isMeta: true) - ツール実行結果(
tool_resultタイプ) - コンテキスト情報のみのメッセージ
4. セッション識別
Claude Codeのセッションログでは:
- セッションIDの先頭8文字を抽出
- フォルダ名も含めて識別
- ファイル名:
<フォルダ名>_chat_<セッションID>.md
例:my-project_chat_abc12345.md
5. 安全なファイル名生成
Windows/Mac/Linuxで使えない文字を自動変換:
\ / : * ? " < > |→_
使い方
ステップ1: Claude Codeのセッションログを見つける
# Windowsの場合
C:\Users\<ユーザー名>\.claude\sessions\
# macOS/Linuxの場合
~/.claude/sessions/各セッションフォルダ内に transcript.jsonl があります。
ステップ2: エクスポーターを配置
# フォルダ作成
mkdir C:\my-local\my-python\claude_chat_exporter
cd C:\my-local\my-python\claude_chat_exporter
# スクリプト配置(後述のコードを保存)Windows ユーザー向け: ドラッグ&ドロップで実行するために、exporter.bat も作成してください:
@echo off
python "%~dp0exporter.py" %*
pauseこのバッチファイルを exporter.py と同じフォルダに保存することで、ファイルをドラッグ&ドロップして実行できるようになります。
ステップ3: 実行
方法1: ドラッグ&ドロップ(一番簡単!)
Windows の場合: .py ファイルへのドラッグ&ドロップは関連付けの問題で動作しないことがあります。その場合は exporter.bat(後述)を使用してください。
- 必要な
transcript.jsonlファイルを複数選択 exporter.bat(Windows)またはexporter.py(Mac/Linux)にドラッグ&ドロップ- 自動処理完了!
方法2: ダイアログ
python exporter.pyファイル選択ダイアログで複数のJSON/JSONLファイルを選択
方法3: コマンドライン
# 単一ファイル
python exporter.py C:\Users\user\.claude\sessions\abc123\transcript.jsonl
# 複数ファイル
python exporter.py session1\transcript.jsonl session2\transcript.jsonl
# ワイルドカード(PowerShell)
python exporter.py (Get-ChildItem -Path "C:\Users\user\.claude\sessions\*\transcript.jsonl" -Recurse).FullNameステップ4: 出力確認
# 出力先
output\20260215_100000\
├── session1_chat_abc12345.md
├── session2_chat_def67890.md
└── project-x_chat_ghi11111.mdソースコード全文
exporter.py
import json
import sys
import tkinter.filedialog as fd
from datetime import datetime
from pathlib import Path
import re
def clean_system_tags(text):
"""Claude Codeのシステムタグを除去"""
# システムタグのパターン(開始タグと終了タグのペア)
paired_tags = [
r'.*? ',
r'.*? ',
r'.*? ',
r'.*? ',
r'.*? ',
r'.*? ',
r'.*? ',
r'.*? ',
]
# 各パターンを除去(DOTALLフラグで改行を含む)
for pattern in paired_tags:
text = re.sub(pattern, '', text, flags=re.DOTALL)
# 連続する空行を1つにまとめる
text = re.sub(r'\n{3,}', '\n\n', text)
return text.strip()
def process_jsonl_file(file_path, output_dir):
"""JSONL形式(Claude Codeのログ)の処理"""
messages = []
session_id = None
# 各行を読み込んでパース
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if not line:
continue
try:
entry = json.loads(line)
# セッションID取得(タイトル用)
if session_id is None and 'sessionId' in entry:
session_id = entry['sessionId']
# ユーザーまたはアシスタントのメッセージのみ抽出
if entry.get('type') in ['user', 'assistant'] and 'message' in entry:
msg = entry['message']
# isMeta=trueのメッセージはスキップ(システムメッセージ)
if entry.get('isMeta', False):
continue
role = msg.get('role', '')
content = msg.get('content', '')
# contentが配列形式の場合
if isinstance(content, list):
# tool_resultはスキップ(Task実行結果など)
if any(item.get('type') == 'tool_result' for item in content):
continue
# textタイプのコンテンツのみ抽出(user/assistant共通)
text_parts = [item.get('text', '') for item in content if item.get('type') == 'text']
content = '\n\n'.join(text_parts)
# システムタグを除去
content = clean_system_tags(content) if isinstance(content, str) else content
if content and role:
# roleを表示形式に変換
sender = 'human' if role == 'user' else 'assistant'
messages.append({'sender': sender, 'text': content})
except json.JSONDecodeError:
continue
# メッセージが1つもない場合はスキップ
if not messages:
print(f"スキップ: メッセージが含まれていません ({file_path.name})")
return
# フォルダ名とタイトル生成
folder_name = file_path.parent.name
if session_id:
title = f"chat_{session_id[:8]}"
else:
title = file_path.stem
# ファイル名に使えない文字を除去(フォルダ名_タイトル.md)
safe_folder_name = re.sub(r'[\\/:*?"<>|]', '_', folder_name)
safe_title = re.sub(r'[\\/:*?"<>|]', '_', title)
filename = f"{safe_folder_name}_{safe_title}.md"
# Markdownファイルに出力
with open(output_dir / filename, 'w', encoding='utf-8') as f:
f.write(f"# {safe_folder_name}_{safe_title}\n\n")
for m in messages:
f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n")
print(f"処理完了: {filename}")
def process_json_file(file_path, output_dir):
"""JSON形式(conversations.json)の処理"""
try:
d = json.load(open(file_path, 'r', encoding='utf-8'))
except:
d = json.load(open(file_path, 'r', encoding='cp932'))
# チャット毎にファイル出力
for i, c in enumerate(d):
# タイトル取得(なければ chat_N)
title = c.get('name', f'chat_{i}')
# フォルダ名を取得
folder_name = file_path.parent.name
safe_folder_name = re.sub(r'[\\/:*?"<>|]', '_', folder_name)
safe_title = re.sub(r'[\\/:*?"<>|]', '_', title)
filename = f"{safe_folder_name}_{safe_title}.md"
# Markdownファイルに出力
with open(output_dir / filename, 'w', encoding='utf-8') as f:
# タイトルを見出しとして出力
f.write(f"# {safe_folder_name}_{safe_title}\n\n")
# メッセージを順番に出力
for m in c.get('chat_messages', []):
f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n") # human または assistant
print(f"処理完了: {filename}")
def main():
# 出力フォルダ作成(pyファイルと同じ場所/output/yyyymmdd_hh24miss)
output_dir = Path(__file__).parent / 'output' / datetime.now().strftime('%Y%m%d_%H%M%S')
output_dir.mkdir(parents=True, exist_ok=True)
# ファイルパスの取得(コマンドライン引数 or ダイアログ)
if len(sys.argv) > 1:
# コマンドライン引数またはドラッグ&ドロップから取得
file_paths = [Path(arg) for arg in sys.argv[1:] if Path(arg).exists()]
if not file_paths:
print("エラー: 有効なファイルパスが指定されていません")
input("Enterキーを押して終了...")
return
else:
# ダイアログで複数ファイル選択
paths = fd.askopenfilenames(filetypes=[("JSON/JSONL files", "*.json;*.jsonl")])
if not paths:
return
file_paths = [Path(p) for p in paths]
# 各ファイルを処理
for file_path in file_paths:
print(f"\n処理中: {file_path.name}")
if file_path.suffix == '.jsonl':
process_jsonl_file(file_path, output_dir)
elif file_path.suffix == '.json':
process_json_file(file_path, output_dir)
else:
print(f"スキップ: 未対応のファイル形式 ({file_path.name})")
print(f"\n全ての処理が完了しました")
print(f"出力先: {output_dir}")
input("\nEnterキーを押して終了...")
if __name__ == "__main__":
main()exporter.bat(Windows用)
@echo off
python "%~dp0exporter.py" %*
pause説明:
%~dp0: バッチファイルのあるフォルダパス(末尾に\付き)%*: ドラッグ&ドロップされたすべてのファイルパスpause: 実行後にウィンドウを閉じずに結果を確認できる
コードの詳細解説
1. JSONL処理のコア部分
def process_jsonl_file(file_path, output_dir):
messages = []
session_id = None
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
# 各行をJSONとしてパース
entry = json.loads(line)ポイント:
- JSONL形式は1行=1JSONオブジェクト
json.loads()で各行を個別にパース- セッション全体で必要な情報を蓄積
2. 賢いフィルタリング
# システムメッセージを除外
if entry.get('isMeta', False):
continue
# ツール実行結果を除外
if isinstance(content, list):
if any(item.get('type') == 'tool_result' for item in content):
continue効果:
- 人間とアシスタントの会話のみを抽出
- ファイル編集やコマンド実行の中間結果をスキップ
- 読みやすい会話ログを生成
3. システムタグの除去
def clean_system_tags(text):
"""Claude Codeのシステムタグを除去"""
paired_tags = [
r'.*? ',
r'.*? ',
# ... その他のタグ
]
for pattern in paired_tags:
text = re.sub(pattern, '', text, flags=re.DOTALL)
return text.strip()必要性:
- Claude Codeのログには
やなどのシステムタグが含まれる - これらは内部処理用の情報で、会話ログには不要
- 正規表現で自動除去することで、純粋な会話内容だけを抽出
4. 配列形式のコンテンツ処理
if isinstance(content, list):
# textタイプのコンテンツのみ抽出(user/assistant共通)
text_parts = [item.get('text', '') for item in content if item.get('type') == 'text']
content = '\n\n'.join(text_parts)
# システムタグを除去
content = clean_system_tags(content) if isinstance(content, str) else contentClaude Codeの特性:
- user/assistantともに配列形式でメッセージが格納されることがある
- ツール使用の説明と結果が分かれて格納される
- textタイプのみを抽出して結合することで、会話部分を取得
やなどのシステムタグを自動除去
4. セッション識別
# セッションID取得
if session_id is None and 'sessionId' in entry:
session_id = entry['sessionId']
# フォルダ名とセッションIDで識別
folder_name = file_path.parent.name
title = f"chat_{session_id[:8]}"
filename = f"{safe_folder_name}_{safe_title}.md"実用的な識別:
- セッションIDだけでは識別困難
- フォルダ名(プロジェクト名など)を含めることで識別性向上
- 先頭8文字のみ使用して簡潔に
5. 複数ファイル処理
if len(sys.argv) > 1:
# コマンドライン引数またはドラッグ&ドロップ
file_paths = [Path(arg) for arg in sys.argv[1:] if Path(arg).exists()]
else:
# ダイアログで複数選択
paths = fd.askopenfilenames(filetypes=[("JSON/JSONL files", "*.json;*.jsonl")])利便性:
- 複数の入力方法に対応
- ドラッグ&ドロップで直感的に処理
- スクリプト化も可能
出力例
入力: transcript.jsonl(Claude Codeセッション)
{"type":"user","sessionId":"abc123def456","message":{"role":"user","content":"Pythonで素数判定関数を作成して"}}
{"type":"assistant","sessionId":"abc123def456","message":{"role":"assistant","content":[{"type":"text","text":"素数判定関数を作成します..."}]}}出力: my-project_chat_abc12345.md
# my-project_chat_abc12345
**[human]**:
Pythonで素数判定関数を作成して
**[assistant]**:
素数判定関数を作成します...実際の使用場面
1. プロジェクト開発の記録
# プロジェクトフォルダのセッションを一括エクスポート
cd C:\Users\user\.claude\sessions
python exporter.py my-web-app\transcript.jsonl api-server\transcript.jsonl活用方法:
- プロジェクト毎の開発履歴を整理
- 設計判断の理由を後から確認
- 新メンバーへのオンボーディング資料
2. トラブルシューティングの記録
エラー解決のセッションを保存:
python exporter.py bug-fix-session\transcript.jsonl活用方法:
- 同じエラーが再発した時の解決策を即座に参照
- チーム内でトラブル事例を共有
- ナレッジベースに追加
3. 学習記録
新しい技術を学んだセッションを整理:
# Claude Codeで学習したセッションをまとめてエクスポート
python exporter.py react-learning\transcript.jsonl typescript-basics\transcript.jsonl活用方法:
- 学習過程を振り返る
- 理解した内容を復習
- ブログ記事の素材
4. 週次レビュー
1週間のセッションを一括処理:
# PowerShellで過去7日間のセッションを抽出
$sessions = Get-ChildItem -Path "C:\Users\user\.claude\sessions\*\transcript.jsonl" -Recurse |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) }
python exporter.py $sessions.FullName活用方法:
- 週の振り返りMTG資料
- 進捗報告の補助資料
- 学んだことの整理
5. バックアップと保存
定期的なバックアップスクリプト:
# バッチファイル例(backup_sessions.bat)
@echo off
set OUTPUT_DIR=D:\claude_backups\%date:~0,4%%date:~5,2%%date:~8,2%
mkdir %OUTPUT_DIR%
python exporter.py C:\Users\user\.claude\sessions\*\transcript.jsonl
move output\* %OUTPUT_DIR%応用アイデア
1. 自動分類スクリプト
# タグ別にフォルダ分け
import shutil
tag_keywords = {
'Python': ['python', 'django', 'flask'],
'JavaScript': ['javascript', 'react', 'node'],
'Database': ['sql', 'postgres', 'mysql'],
}
for md_file in output_dir.glob('*.md'):
content = md_file.read_text(encoding='utf-8')
for tag, keywords in tag_keywords.items():
if any(kw in content.lower() for kw in keywords):
tag_dir = output_dir / tag
tag_dir.mkdir(exist_ok=True)
shutil.move(md_file, tag_dir / md_file.name)
break2. 統計情報の抽出
# セッションの統計
def analyze_session(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
human_msgs = content.count('**[human]**')
assistant_msgs = content.count('**[assistant]**')
total_chars = len(content)
print(f"{file_path.name}:")
print(f" 人間: {human_msgs}回, Claude: {assistant_msgs}回")
print(f" 総文字数: {total_chars}")3. Obsidian/Notion連携
Markdown出力なので、そのままObsidianやNotionにインポート可能:
# Obsidian vaultにコピー
cp output/20260215_100000/*.md ~/ObsidianVault/Claude-Sessions/4. 全文検索インデックス
# 簡易検索機能
import glob
def search_sessions(keyword, output_dir):
results = []
for md_file in Path(output_dir).glob('**/*.md'):
content = md_file.read_text(encoding='utf-8')
if keyword.lower() in content.lower():
results.append(md_file)
return results
# 使用例
found = search_sessions('素数判定', 'output/20260215_100000')
for f in found:
print(f"見つかりました: {f.name}")トラブルシューティング
エラー1: JSONDecodeError
原因: JSONL形式が壊れている(途中で保存が中断された等)
解決方法:
# スクリプトは自動でスキップするため、通常は問題なし
except json.JSONDecodeError:
continue # 壊れた行はスキップエラー2: セッションIDが見つからない
症状: ファイル名が <フォルダ名>_transcript.md になる
原因: セッションIDが記録されていない(古い形式等)
対処: ファイル名(transcript)で識別されるため問題なし
エラー3: メッセージが含まれていません
原因: ツール実行結果のみでユーザー/アシスタントの会話がない
対処: これは正常な動作(処理がスキップされる)
エラー4: UnicodeDecodeError
原因: 特殊な文字が含まれている
解決方法:
# open時にerrors='ignore'を追加
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:まとめ
この拡張版Claude Chat Exporterを使えば:
- ✅ Claude Codeの全セッションを一括でMarkdown化
- ✅ 読みやすい形式で長期保存・検索可能に
- ✅ プロジェクトの開発履歴として資産化
- ✅ チーム内でのナレッジ共有が簡単に
- ✅ 従来のWebチャットにも引き続き対応
Claude Codeを使えば使うほど、貴重な知的財産が蓄積されます。このツールで効率的に管理・活用して、開発の生産性を更に高めましょう。
関連記事
以前のバージョン(Webチャット専用)の解説記事:

GitHubでの管理
このツールをGitHubで管理する方法:
cd C:\my-local\my-python\claude_chat_exporter
# .gitignoreを作成
cat > .gitignore << 'EOF'
# 出力ファイル
output/
*.json
*.jsonl
# Python
__pycache__/
*.pyc
# エディタ
.vscode/
.idea/
EOF
# Git初期化
git init
git add exporter.py .gitignore
git commit -m "feat: Claude Code session exporter"
# GitHubにプッシュ(GitHub CLI使用)
gh repo create claude-chat-exporter --private --source=. --pushGitHub連携の詳細は以下の記事を参照:

ご意見・改善提案があれば、ぜひコメントでお知らせください!
コメント