Search across all Claude Code session transcripts by keyword, date, or regex pattern
Search across all Claude Code session transcripts by keyword, date, or regex pattern to find past conversations, implementations, or error discussions. Use this when you need to locate specific discussions from previous sessions or resume related work.
/plugin marketplace add melodic-software/claude-code-plugins/plugin install claude-code-observability@melodic-software<query> [--days N] [--project] [--regex] [--context N]Search across all Claude Code session transcripts to find past conversations, implementations, or patterns.
| Argument | Description |
|---|---|
<query> | Search term or pattern to find |
--days N | Limit search to last N days (default: all) |
--project | Search current project only (default: all projects) |
--regex | Treat query as regex pattern |
--context N | Show N lines of context around matches (default: 2) |
--files-only | Only show matching session files, not content |
~/.claude/projects/
├── D--repos-project-a/
│ ├── abc123.jsonl # Session files
│ ├── def456.jsonl
│ └── agent-xyz789.jsonl # Agent transcripts
├── D--repos-project-b/
│ └── ...
| Content Type | Searched |
|---|---|
| User messages | ✅ Yes |
| Assistant responses | ✅ Yes |
| Tool call content | ✅ Yes |
| Compaction summaries | ✅ Yes |
| File paths in context | ✅ Yes |
from pathlib import Path
from datetime import datetime, timezone, timedelta
claude_dir = Path.home() / ".claude"
projects_dir = claude_dir / "projects"
# Get all session files
session_files = []
if project_only:
# Current project only
current_project = Path.cwd()
project_encoded = str(current_project).replace(":", "").replace("/", "-").replace("\\", "-")
project_dir = projects_dir / project_encoded
if project_dir.exists():
session_files = list(project_dir.glob("*.jsonl"))
else:
# All projects
session_files = list(projects_dir.glob("**/*.jsonl"))
# Filter by date if specified
if days_limit:
cutoff = datetime.now(timezone.utc) - timedelta(days=days_limit)
session_files = [
f for f in session_files
if datetime.fromtimestamp(f.stat().st_mtime, tz=timezone.utc) > cutoff
]
import json
import re
def search_session(session_path, query, use_regex=False):
"""Search a session file for matches."""
matches = []
if use_regex:
pattern = re.compile(query, re.IGNORECASE)
else:
pattern = None
with open(session_path) as f:
for line_num, line in enumerate(f, 1):
try:
record = json.loads(line)
except json.JSONDecodeError:
continue
# Extract searchable text
text = ""
record_type = record.get("type", "")
if record_type == "user":
text = record.get("message", {}).get("content", "")
elif record_type == "assistant":
text = record.get("message", {}).get("content", "")
elif record_type == "summary":
text = record.get("summary", "")
# Check for match
if use_regex:
if pattern.search(text):
matches.append({
"line": line_num,
"type": record_type,
"text": text,
"snippet": get_snippet(text, query)
})
else:
if query.lower() in text.lower():
matches.append({
"line": line_num,
"type": record_type,
"text": text,
"snippet": get_snippet(text, query)
})
return matches
def get_snippet(text, query, context_chars=100):
"""Extract snippet around match."""
idx = text.lower().find(query.lower())
if idx == -1:
return text[:200]
start = max(0, idx - context_chars)
end = min(len(text), idx + len(query) + context_chars)
snippet = text[start:end]
if start > 0:
snippet = "..." + snippet
if end < len(text):
snippet = snippet + "..."
return snippet
def format_results(results, query):
"""Format search results for display."""
total_matches = sum(len(r["matches"]) for r in results)
print(f"# Transcript Search: \"{query}\"\n")
print(f"Found {total_matches} match(es) in {len(results)} session(s)\n")
for result in results[:20]: # Limit to 20 sessions
session = result["session"]
matches = result["matches"]
mtime = result["modified"]
print(f"## {session.parent.name}/{session.name}")
print(f"Modified: {mtime.strftime('%Y-%m-%d %H:%M')}")
print(f"Matches: {len(matches)}\n")
for match in matches[:5]: # Limit to 5 matches per session
print(f" Line {match['line']} ({match['type']}):")
print(f" {match['snippet']}\n")
if len(matches) > 5:
print(f" ... and {len(matches) - 5} more matches\n")
# Show resume command
session_id = session.stem
print(f" Resume: `claude --resume {session_id}`\n")
# Transcript Search: "authentication middleware"
Found 7 matches in 3 sessions
---
## D--repos-web-app/abc123-def456.jsonl
**Modified:** 2025-12-28 14:30
**Matches:** 4
**Line 156 (user):**
> ...implement authentication middleware for the API endpoints...
**Line 203 (assistant):**
> ...I'll create the authentication middleware in `src/middleware/auth.ts`...
**Resume:** `claude --resume abc123-def456`
---
## D--repos-api-server/xyz789.jsonl
**Modified:** 2025-12-25 10:00
**Matches:** 2
**Line 89 (user):**
> ...the authentication middleware isn't validating tokens correctly...
**Resume:** `claude --resume xyz789`
---
## Quick Actions
- View full session: `claude --resume <session-id>`
- Search current project only: `/user-config:transcript-search "query" --project`
- Search with regex: `/user-config:transcript-search "auth.*middleware" --regex`
- Narrow by date: `/user-config:transcript-search "query" --days 7`
| Goal | Query Example |
|---|---|
| Find specific error | "ENOENT: no such file" |
| Find implementation | "middleware authentication" |
| Find by file path | src/components/Button |
| Find tool usage | "Edit tool" |
| Find decisions | "decided to use" |
| Find failures | "let me try again" |
--project for faster current-project searches--days N to limit search scope/user-config:retrospective - Analyze specific session/user-config:prompt-extract - Extract good prompts/user-config:session-stats - View session statistics/list-sessions - List all sessionsThis command uses the user-config-management skill for: