Skip to content

Instantly share code, notes, and snippets.

@gchamon
Created October 24, 2025 14:29
Show Gist options
  • Select an option

  • Save gchamon/0949dc427086d387e9164b229271288b to your computer and use it in GitHub Desktop.

Select an option

Save gchamon/0949dc427086d387e9164b229271288b to your computer and use it in GitHub Desktop.
claude-session

Claude Code Session Extractor

A tool to extract and query Claude Code session information for easy restoration with claude -r.

Quick Start

# List all sessions
python3 ~/.claude/extract-sessions.py

# List sessions grouped by project
python3 ~/.claude/extract-sessions.py --by-project

# List sessions for a specific project
python3 ~/.claude/extract-sessions.py --project /path/to/project

# Limit results
python3 ~/.claude/extract-sessions.py --limit 10

Usage with jq

Find session by keyword in session name

python3 ~/.claude/extract-sessions.py | \
  jq -r '.sessions[] | select(.session_name | contains("keyword")) | .session_id'

Get the most recent session for a project

python3 ~/.claude/extract-sessions.py \
  --project /home/user/my-project \
  --limit 1 | \
  jq -r '.sessions[0].session_id'

List all projects

python3 ~/.claude/extract-sessions.py --by-project | jq -r 'keys[]'

Get recent sessions for each project

python3 ~/.claude/extract-sessions.py --by-project --limit 5 | \
  jq -r 'to_entries[] | "\(.key): \(.value | length) sessions"'

Restore a session

Once you have the session ID, restore it with:

claude -r <session-id>

Output Format

Default format (flat list)

{
  "sessions": [
    {
      "session_id": "uuid",
      "session_name": "First user message in session",
      "project": "/absolute/path/to/project",
      "timestamp": 1234567890123,
      "date": "2025-10-24T11:16:54.970000"
    }
  ]
}

By-project format

{
  "/absolute/path/to/project": [
    {
      "session_id": "uuid",
      "session_name": "First user message",
      "timestamp": 1234567890123,
      "date": "2025-10-24T11:16:54.970000"
    }
  ]
}

Command Line Options

  • --claude-dir PATH: Path to Claude Code data directory (default: ~/.claude)
  • --project PATH: Filter sessions by project path
  • --by-project: Group output by project
  • --limit N: Limit number of sessions returned (per project when using --by-project)

How It Works

The script correlates data from two sources:

  1. history.jsonl: Contains user prompts with timestamps and project paths
  2. debug/*.txt: Session debug logs with UUIDs (session IDs)

It matches sessions to history entries using timestamp proximity (within 5 minutes) to determine:

  • Session ID (UUID from debug filename)
  • Session name (first user message from history)
  • Project path (from history entry)
  • Timestamp and human-readable date

Examples

Create a shell function for easy access

Add to your .bashrc or .zshrc:

# List Claude sessions
claude-sessions() {
  python3 ~/.claude/extract-sessions.py "$@"
}

# Find and restore Claude session by keyword
claude-find() {
  local keyword="$1"
  local session_id=$(python3 ~/.claude/extract-sessions.py | \
    jq -r ".sessions[] | select(.session_name | contains(\"$keyword\")) | .session_id" | \
    head -1)

  if [ -n "$session_id" ]; then
    echo "Found session: $session_id"
    claude -r "$session_id"
  else
    echo "No session found matching: $keyword"
  fi
}

# List recent sessions for current directory
claude-recent() {
  local pwd=$(pwd)
  python3 ~/.claude/extract-sessions.py --project "$pwd" --limit 10 | \
    jq -r '.sessions[] | "\(.date) - \(.session_name[:80])"'
}

Interactive session browser with fzf

# Install fzf first: https://github.com/junegunn/fzf

claude-browse() {
  local session_id=$(python3 ~/.claude/extract-sessions.py --by-project | \
    jq -r 'to_entries[] | .value[] | "\(.date) [\(.session_id)] \(.session_name)"' | \
    fzf --height=50% --reverse --preview-window=hidden | \
    grep -oP '\[([a-f0-9-]+)\]' | tr -d '[]')

  if [ -n "$session_id" ]; then
    claude -r "$session_id"
  fi
}

Limitations

  • Session correlation is based on timestamp proximity (5 minute window)
  • Sessions without history entries won't have proper names
  • Very old sessions may not correlate correctly if system time changed
  • The first message may not always be the most descriptive session name

Troubleshooting

No sessions found:

  • Check that ~/.claude/history.jsonl exists and has entries
  • Check that ~/.claude/debug/ contains .txt files
  • Try running without filters first

Incorrect session names:

  • The correlation window is 5 minutes; if clock was adjusted, matches may be off
  • Some sessions may have been created without history entries

Permission errors:

  • Ensure the script is executable: chmod +x ~/.claude/extract-sessions.py
  • Ensure you have read access to ~/.claude/ directory
#!/usr/bin/env bash
# Claude Code session management utility
# Wrapper around extract-sessions.py for easier CLI usage
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXTRACTOR="$SCRIPT_DIR/extract-sessions.py"
usage() {
cat <<EOF
Usage: claude-sessions [COMMAND] [OPTIONS]
Commands:
list [--by-project] [--limit N] [--project PATH]
List all sessions (default command)
find KEYWORD
Find sessions by keyword and display with session IDs
restore KEYWORD
Find and restore a session by keyword (launches claude -r)
projects
List all projects
recent [PROJECT_PATH]
Show recent sessions for a project (defaults to current directory)
help
Show this help message
Options:
--limit N Limit number of results
--project PATH Filter by project path
--by-project Group results by project
Examples:
# List all sessions
claude-sessions list
# List sessions grouped by project
claude-sessions list --by-project
# Find sessions containing "invoice"
claude-sessions find invoice
# Restore a session by keyword
claude-sessions restore "implement dark mode"
# List all projects
claude-sessions projects
# Show recent sessions for current directory
claude-sessions recent
# Show recent sessions for specific project
claude-sessions recent /path/to/project
EOF
}
cmd_list() {
python3 "$EXTRACTOR" "$@"
}
cmd_find() {
local keyword="$1"
if [ -z "$keyword" ]; then
echo "Error: keyword required" >&2
echo "Usage: claude-sessions find KEYWORD" >&2
exit 1
fi
python3 "$EXTRACTOR" | \
jq -r ".sessions[] | select(.session_name | contains(\"$keyword\")) | \"\(.session_id) | \(.date) | \(.session_name[:80])\""
}
cmd_restore() {
local keyword="$1"
if [ -z "$keyword" ]; then
echo "Error: keyword required" >&2
echo "Usage: claude-sessions restore KEYWORD" >&2
exit 1
fi
local session_id=$(python3 "$EXTRACTOR" | \
jq -r ".sessions[] | select(.session_name | contains(\"$keyword\")) | .session_id" | \
head -1)
if [ -n "$session_id" ]; then
echo "Restoring session: $session_id"
claude -r "$session_id"
else
echo "No session found matching: $keyword" >&2
exit 1
fi
}
cmd_projects() {
python3 "$EXTRACTOR" --by-project | jq -r 'keys[]'
}
cmd_recent() {
local project="${1:-$(pwd)}"
python3 "$EXTRACTOR" --project "$project" --limit 10 | \
jq -r '.sessions[] | "\(.date) | \(.session_name[:80])"'
}
# Main command dispatcher
case "${1:-list}" in
list)
shift
cmd_list "$@"
;;
find)
shift
cmd_find "$@"
;;
restore)
shift
cmd_restore "$@"
;;
projects)
cmd_projects
;;
recent)
shift
cmd_recent "$@"
;;
help|--help|-h)
usage
;;
*)
echo "Error: Unknown command '$1'" >&2
echo "Run 'claude-sessions help' for usage information" >&2
exit 1
;;
esac
#!/usr/bin/env python3
"""
Extract Claude Code session information for easy querying and restoration.
This script parses the Claude Code data directory to extract:
- Session ID (UUID)
- Session name (first user message)
- Project path
- Timestamp
Output is JSON format suitable for querying with jq or other tools.
"""
import json
import os
from pathlib import Path
from typing import Dict, List, Optional
from collections import defaultdict
from datetime import datetime
def parse_history_jsonl(history_file: Path) -> List[Dict]:
"""Parse history.jsonl and return list of entries."""
entries = []
if not history_file.exists():
return entries
with open(history_file, 'r') as f:
for line in f:
line = line.strip()
if line:
try:
entries.append(json.loads(line))
except json.JSONDecodeError:
continue
return entries
def get_debug_sessions(debug_dir: Path) -> Dict[str, datetime]:
"""Get session IDs from debug directory with their modification times."""
sessions = {}
if not debug_dir.exists():
return sessions
for debug_file in debug_dir.glob("*.txt"):
# Skip the 'latest' symlink
if debug_file.name == "latest":
continue
# Extract UUID from filename (remove .txt extension)
session_id = debug_file.stem
# Get file modification time
mtime = datetime.fromtimestamp(debug_file.stat().st_mtime)
sessions[session_id] = mtime
return sessions
def correlate_sessions(history_entries: List[Dict], sessions: Dict[str, datetime]) -> List[Dict]:
"""
Correlate history entries with sessions to build session database.
Groups consecutive history entries by project and timestamp proximity
to identify distinct sessions.
"""
# Group history entries by project
project_entries = defaultdict(list)
for entry in history_entries:
project = entry.get("project")
if project:
project_entries[project].append(entry)
# Build session records
session_records = []
# For each session ID found in debug directory
for session_id, session_time in sorted(sessions.items(), key=lambda x: x[1]):
# Try to find matching history entries
# Look for entries within a reasonable time window (±5 minutes)
session_timestamp_ms = int(session_time.timestamp() * 1000)
time_window_ms = 5 * 60 * 1000 # 5 minutes
matching_entries = []
session_project = None
for project, entries in project_entries.items():
for entry in entries:
entry_time = entry.get("timestamp", 0)
if abs(entry_time - session_timestamp_ms) < time_window_ms:
matching_entries.append(entry)
session_project = project
# Use the first matching entry as the session name
if matching_entries:
# Sort by timestamp to get the first message
matching_entries.sort(key=lambda x: x.get("timestamp", 0))
first_entry = matching_entries[0]
session_name = first_entry.get("display", "").strip()
session_project = first_entry.get("project")
session_timestamp = first_entry.get("timestamp")
session_records.append({
"session_id": session_id,
"session_name": session_name[:100], # Truncate long names
"project": session_project,
"timestamp": session_timestamp,
"date": datetime.fromtimestamp(session_timestamp / 1000).isoformat() if session_timestamp else None
})
return session_records
def extract_sessions_by_project(session_records: List[Dict]) -> Dict[str, List[Dict]]:
"""Group sessions by project."""
by_project = defaultdict(list)
for record in session_records:
project = record.get("project")
if project:
by_project[project].append({
"session_id": record["session_id"],
"session_name": record["session_name"],
"timestamp": record["timestamp"],
"date": record["date"]
})
# Sort each project's sessions by timestamp (newest first)
for project in by_project:
by_project[project].sort(key=lambda x: x.get("timestamp", 0), reverse=True)
return dict(by_project)
def main():
"""Main entry point."""
import argparse
parser = argparse.ArgumentParser(
description="Extract Claude Code session information",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# List all sessions as JSON
%(prog)s
# List sessions for a specific project
%(prog)s --project /path/to/project
# Get session ID for restoration
%(prog)s | jq -r '.sessions[] | select(.session_name | contains("search term")) | .session_id'
# List all projects
%(prog)s --by-project | jq -r 'keys[]'
"""
)
parser.add_argument(
"--claude-dir",
type=Path,
default=Path.home() / ".claude",
help="Path to Claude Code data directory (default: ~/.claude)"
)
parser.add_argument(
"--project",
type=str,
help="Filter sessions by project path"
)
parser.add_argument(
"--by-project",
action="store_true",
help="Group output by project"
)
parser.add_argument(
"--limit",
type=int,
help="Limit number of sessions returned per project"
)
args = parser.parse_args()
# Parse data
history_file = args.claude_dir / "history.jsonl"
debug_dir = args.claude_dir / "debug"
history_entries = parse_history_jsonl(history_file)
sessions = get_debug_sessions(debug_dir)
# Correlate and build session records
session_records = correlate_sessions(history_entries, sessions)
# Apply filters
if args.project:
session_records = [r for r in session_records if r.get("project") == args.project]
# Format output
if args.by_project:
output = extract_sessions_by_project(session_records)
# Apply limit per project
if args.limit:
for project in output:
output[project] = output[project][:args.limit]
print(json.dumps(output, indent=2))
else:
# Sort by timestamp (newest first)
session_records.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
# Apply limit
if args.limit:
session_records = session_records[:args.limit]
print(json.dumps({"sessions": session_records}, indent=2))
if __name__ == "__main__":
main()

Claude Code Session Management Tools

This directory contains tools for extracting and managing Claude Code sessions programmatically.

Quick Start

# List all projects
~/.claude/claude-sessions projects

# Find sessions by keyword
~/.claude/claude-sessions find "keyword"

# Show recent sessions for current directory
~/.claude/claude-sessions recent

# Restore a session by keyword
~/.claude/claude-sessions restore "keyword"

Files

extract-sessions.py

Python script that extracts session data from Claude Code's data directory.

Outputs JSON with:

  • session_id: UUID for use with claude -r
  • session_name: First user message in the session
  • project: Absolute path to the project
  • timestamp: Unix timestamp in milliseconds
  • date: ISO format date string

Usage:

python3 extract-sessions.py [--by-project] [--limit N] [--project PATH]

claude-sessions

Bash wrapper script providing user-friendly commands.

Commands:

  • list: List all sessions (default)
  • find KEYWORD: Find sessions by keyword
  • restore KEYWORD: Find and restore a session
  • projects: List all projects
  • recent [PATH]: Show recent sessions for a project
  • help: Show help message

README-extract-sessions.md

Comprehensive documentation with examples, shell functions, and troubleshooting.

Installation (Optional)

Add to your PATH for easy access:

# Add to ~/.bashrc or ~/.zshrc
export PATH="$HOME/.claude:$PATH"

# Or create a symlink
ln -s ~/.claude/claude-sessions ~/.local/bin/claude-sessions

Example Workflows

Find and restore a session

# Method 1: Using the wrapper
claude-sessions find "dark mode"
# Copy the session ID from output
claude -r <session-id>

# Method 2: One-liner
claude-sessions restore "dark mode"

Browse sessions with jq

# Find sessions in a specific project
python3 ~/.claude/extract-sessions.py \
  --project /path/to/project | \
  jq -r '.sessions[] | "\(.date) - \(.session_name)"'

# Get the most recent session ID for current project
python3 ~/.claude/extract-sessions.py \
  --project "$(pwd)" \
  --limit 1 | \
  jq -r '.sessions[0].session_id'

List sessions per project

# Show session count per project
python3 ~/.claude/extract-sessions.py --by-project | \
  jq -r 'to_entries[] | "\(.value | length)\t\(.key)"' | \
  sort -rn

How It Works

The tools correlate data from two sources:

  1. ~/.claude/history.jsonl: Contains user prompts with timestamps and project paths
  2. ~/.claude/debug/*.txt: Session debug logs named with session UUIDs

Sessions are matched to history entries using timestamp proximity (5 minute window).

Notes

  • Session IDs are UUIDs from the debug log filenames
  • Session names are the first user message from history
  • Projects are tracked by absolute path
  • Timestamps are in Unix milliseconds (from history entries)
  • Sessions without history entries may not appear in results

Integration Examples

Shell Function

Add to ~/.bashrc or ~/.zshrc:

# Quick session restore by keyword
cr() {
  ~/.claude/claude-sessions restore "$*"
}

# List recent sessions for current directory
cls() {
  ~/.claude/claude-sessions recent
}

With fzf (fuzzy finder)

# Install fzf: https://github.com/junegunn/fzf
claude-browse() {
  local session_id=$(python3 ~/.claude/extract-sessions.py | \
    jq -r '.sessions[] | "\(.date) | \(.session_name) | \(.session_id)"' | \
    fzf --height=50% --reverse | \
    awk -F' | ' '{print $3}')

  if [ -n "$session_id" ]; then
    claude -r "$session_id"
  fi
}

Limitations

  • 5-minute correlation window may miss sessions if clock was adjusted
  • Sessions created without user interaction may not appear
  • Multiple sessions started simultaneously may be conflated
  • Session names are truncated at 100 characters

See Also

  • README-extract-sessions.md: Full documentation
  • CLAUDE.md: Claude Code data directory structure
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment