Claude Code Session Data Exploration
Structured queries against ~/.claude/history.jsonl and per-project session files.
Every Claude Code session leaves a trace — these patterns extract signal from it.
Data Layout
Claude Code stores session data in two locations:
| Path | Contents |
|---|---|
|
Global session index — one JSON object per line (timestamp, project, display) |
|
Full conversation content per session — messages, tool calls, results |
Before querying, understand the shape of the data.
inspect a single record
head -1 ~/.claude/history.jsonl | jq '.'
available keys
head -1 ~/.claude/history.jsonl | jq 'keys'
total records
wc -l ~/.claude/history.jsonl
per-project session file sizes — ranked by line count
find ~/.claude/projects -name "*.jsonl" \
| xargs wc -l | sort -rn | head -20
Session Counts & Inventory
total session count
jq -s 'length' ~/.claude/history.jsonl
sessions by project — ranked
jq -s 'group_by(.project)
| map({project: .[0].project, sessions: length})
| sort_by(.sessions) | reverse' \
~/.claude/history.jsonl
unique projects worked on
jq -s '[.[].project] | unique | sort' \
~/.claude/history.jsonl
sessions with pasted content
jq -s '[.[] | select(.display | test("Pasted"))] | length' \
~/.claude/history.jsonl
Activity Timeline
activity log — human-readable dates
jq -s 'sort_by(.timestamp)
| .[]
| {date: (.timestamp/1000 | strftime("%Y-%m-%d %H:%M")),
project: .project,
display: .display}' \
~/.claude/history.jsonl | head -100
first and last session timestamps
jq -s 'sort_by(.timestamp)
| {first: (.[0] | {date: (.timestamp/1000 | strftime("%Y-%m-%d %H:%M")), display: .display}),
last: (.[-1] | {date: (.timestamp/1000 | strftime("%Y-%m-%d %H:%M")), display: .display})}' \
~/.claude/history.jsonl
most active days
jq -s '[.[] | .timestamp/1000 | strftime("%Y-%m-%d")]
| group_by(.)
| map({date: .[0], count: length})
| sort_by(.count) | reverse' \
~/.claude/history.jsonl
sessions per day histogram
jq -s 'group_by(.timestamp/1000 | strftime("%Y-%m-%d"))
| map({date: .[0].timestamp/1000 | strftime("%Y-%m-%d"),
count: length})
| sort_by(.date)' \
~/.claude/history.jsonl
activity by day of week
jq -s '[.[] | .timestamp/1000 | strftime("%A")]
| group_by(.)
| map({day: .[0], count: length})
| sort_by(.count) | reverse' \
~/.claude/history.jsonl
peak coding hours
jq -s '[.[] | .timestamp/1000 | strftime("%H")]
| group_by(.)
| map({hour: .[0], count: length})
| sort_by(.hour)' \
~/.claude/history.jsonl
Search & Filter
sessions matching a keyword (case-insensitive)
jq -s '.[] | select(.display | test("ISE|QRadar|python"; "i"))
| {date: (.timestamp/1000 | strftime("%Y-%m-%d")),
display: .display,
project: .project}' \
~/.claude/history.jsonl
sessions in a date range
jq -s '.[]
| select(.timestamp/1000 | strftime("%Y-%m-%d") >= "2026-04-01")
| {date: (.timestamp/1000 | strftime("%Y-%m-%d %H:%M")),
display: .display}' \
~/.claude/history.jsonl
word frequency in session displays
jq -s '[.[].display]
| join(" ")
| ascii_downcase
| split(" ")[]' \
~/.claude/history.jsonl \
| sort | uniq -c | sort -rn | head -30
search full session content for a keyword
grep -rl "keyword" ~/.claude/projects/ \
| xargs jq -r 'select(.type == "say") | .text' 2>/dev/null \
| grep -i "keyword" | head -20
Deep Dive — Full Session Content
The per-project .jsonl files contain the full conversation: user messages, assistant responses, tool calls, and results.
The encoded path replaces / with - and prepends a -.
most recent session file for this project
ls -t ~/.claude/projects/-home-evanusmodestus-atelier--bibliotheca-domus-captures/*.jsonl \
| head -1
read the most recent session
jq '.' "$(ls -t ~/.claude/projects/-home-evanusmodestus-atelier--bibliotheca-domus-captures/*.jsonl | head -1)" \
| head -100
extract only assistant messages
jq 'select(.type == "say") | .text' \
"$(ls -t ~/.claude/projects/-home-evanusmodestus-atelier--bibliotheca-domus-captures/*.jsonl | head -1)" \
| head -50
tool calls made in a session
jq 'select(.type == "tool_use") | {tool: .name, input: .input}' \
"$(ls -t ~/.claude/projects/-home-evanusmodestus-atelier--bibliotheca-domus-captures/*.jsonl | head -1)"
session duration estimate (first to last message)
jq -s '{
start: (.[0].timestamp/1000 | strftime("%H:%M")),
end: (.[-1].timestamp/1000 | strftime("%H:%M")),
messages: length
}' "$(ls -t ~/.claude/projects/-home-evanusmodestus-atelier--bibliotheca-domus-captures/*.jsonl | head -1)"