From trousse
MANDATORY gate BEFORE running jq on any .jsonl under ~/.claude/ or reading past CC sessions. Invoke FIRST when introspecting conversations, searching session history, parsing transcripts, or building tools that read ~/.claude/projects/ data. Provides the CC JSONL schema reference and `deglacer` CLI tool — prevents the 54-attempt fumble pattern where Claudes guess at field names. Triggers on 'what happened last session', 'find when we discussed', 'parse session', 'read conversation', 'session history', 'token usage', 'deglacer'. Do NOT use for live session context (use garde-manger) or git history (use git log). (user)
npx claudepluginhub spm1001/batterie-de-savoir --plugin trousseThis skill is limited to using the following tools:
*Deglazing the pan to lift the fond — extracting the good bits from past sessions.*
Analyzes Claude Code session history JSONL files to extract insights, summaries, and patterns from conversations. Processes current project or all sessions with bash, jq, and subagents.
Analyze Claude Code session logs and surface productivity improvements. Extracts thinking blocks, tool usage stats, error patterns, debug trajectories - then generates friendly, actionable recommendations. Triggers on: introspect, session logs, trajectory, analyze sessions, what went wrong, tool usage, thinking blocks, session history, my reasoning, past sessions, what did I do, how can I improve.
References Claude Code session log schema for JSONL files under ~/.claude/projects/. Details record types, tool call/result pairing, subagent file locations, team layouts, and config paths. Use for parsing transcripts, PostToolUse hooks, or building analyzers.
Share bugs, ideas, or general feedback.
Deglazing the pan to lift the fond — extracting the good bits from past sessions.
You are working with Claude Code session data. This includes:
~/.claude/projects/ session dataDo NOT guess at the schema. The CC JSONL format has multiple entry types, triple-duty user entries, streaming-duplicated message.ids, and inconsistent field presence across versions. This reference is the source of truth.
git log / git blame for code change historydeglacer is the CC JSONL extraction CLI, installed as a uv tool. Use it instead of raw jq for structured extraction.
# Install (once):
uv tool install ~/Repos/batterie/deglacer
# Use:
deglacer SESSION.jsonl
deglacer SESSION.jsonl # conversation text (human + assistant)
deglacer --summary SESSION.jsonl # human messages only (what was discussed)
deglacer --with-tools SESSION.jsonl # include tool call summaries
deglacer --with-thinking SESSION.jsonl # include thinking blocks
deglacer --last 5 SESSION.jsonl # last 5 turns only
deglacer --json SESSION.jsonl # structured JSON output
deglacer --stats SESSION.jsonl # session statistics (tokens, models, tools)
deglacer --timeline SESSION.jsonl # timestamped turn log
deglacer --find "search term" # search across recent sessions
deglacer --recent # list recent sessions (default 20)
deglacer --recent 10 # list N most recent
deglacer --today # list today's sessions
deglacer --since 2026-03-25 # sessions since a date
deglacer --with-tools --last 10 SESSION.jsonl # recent turns with tools
deglacer --with-tools --with-thinking --json ... # everything, structured
deglacer --summary --last 5 SESSION.jsonl # quick recap of recent turns
Sessions live at:
~/.claude/projects/{encoded-cwd}/{session-uuid}.jsonl
Where {encoded-cwd} replaces / with - in the project path.
Subagent transcripts: {session-uuid}/subagents/agent-{id}.jsonl.
Find recent sessions:
ls -lt ~/.claude/projects/*/*.jsonl | head -20
Find sessions for a project:
ls -lt ~/.claude/projects/-home-modha-Repos-myproject/*.jsonl
Match session to slug/name:
head -1 SESSION.jsonl | jq '{sessionId, slug, version}'
Each line in a .jsonl file is one JSON object. The .type field discriminates.
| Type | Purpose | Has timestamp? |
|---|---|---|
assistant | Claude's response | Yes |
user | Human msg / tool result / skill injection | Yes |
progress | Streaming bash/hook/agent output | Yes |
system | Turn timing, API errors, slash commands | Yes |
summary | Context compaction | No |
queue-operation | Input typed while Claude busy | Yes |
last-prompt | Records last user text | No |
custom-title | User-set session name | No |
agent-name | Session agent name | No |
file-history-snapshot | File backup state | No |
pr-link | Created PR reference | Yes |
saved_hook_context | Persisted hook output | Yes |
uuid string Unique entry ID
parentUuid string? Previous entry (linked list)
sessionId string Session UUID (matches filename)
timestamp string ISO 8601
cwd string Working directory
gitBranch string Current git branch
version string CC version (e.g. "2.1.85")
slug string Human-readable session name
userType string Always "external"
entrypoint string "cli" (absent in v2.0.x)
isSidechain boolean Side conversation flag
{
"type": "assistant",
"message": {
"id": "msg_...",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-6",
"content": [/* content blocks */],
"stop_reason": "end_turn" | "tool_use" | null,
"usage": {
"input_tokens": 119,
"cache_creation_input_tokens": 18531,
"cache_read_input_tokens": 36004,
"output_tokens": 500
}
},
"requestId": "req_..."
}
Content block types:
{type: "text", text: "..."} — Claude's text{type: "tool_use", id: "toolu_...", name: "Bash", input: {...}} — tool call{type: "thinking", thinking: "...", signature: "..."} — extended thinkingDRAGON: Multiple entries share the same message.id. CC streams
incremental updates. Merge content blocks by message.id, dedup
tool_use blocks by their id field. deglacer handles this automatically.
DRAGON: stop_reason is null in older sessions (pre-v2.1.79).
The user type serves three purposes. Discriminate with:
| Subtype | How to detect | Content shape |
|---|---|---|
| Human message | typeof content === "string", has permissionMode | String |
| Tool result | Has toolUseResult | Array of {type: "tool_result"} |
| Skill/system injection | isMeta: true | Array of {type: "text"} |
Human message:
{
"type": "user",
"message": {"role": "user", "content": "the actual human text"},
"permissionMode": "default",
"promptId": "..."
}
Tool result:
{
"type": "user",
"message": {"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_...", "content": "output text"}
]},
"toolUseResult": {/* shape varies by tool */},
"sourceToolAssistantUUID": "..."
}
toolUseResult shapes:
| Tool | Keys |
|---|---|
| Bash | stdout, stderr, interrupted, isImage, noOutputExpected |
| Bash (large) | + persistedOutputPath, persistedOutputSize |
| Write/Edit | content, filePath, originalFile, structuredPatch, type |
| Read | file, type |
| Agent | agentId, agentType, content, prompt, status, totalDurationMs, totalTokens, totalToolUseCount, usage |
| Error | Bare string: "User rejected tool use" |
The input_tokens field is ONLY the non-cached portion.
Real input = input_tokens + cache_creation_input_tokens + cache_read_input_tokens.
{"type": "summary", "leafUuid": "...", "summary": "short text"}
No uuid, parentUuid, timestamp, version, or sessionId. Three fields only.
.subtype discriminates:
turn_duration: {durationMs, messageCount}api_error: {error: {status, headers, requestID}, retryInMs, retryAttempt}local_command: {content: "...", level: "info"} — slash commandsQuick schema discovery (do this FIRST, not head | jq .):
jq -r '.type' FILE.jsonl | sort | uniq -c | sort -rn
Extract human messages:
jq -r 'select(.type == "user" and .permissionMode and (.isMeta | not))
| .message.content' FILE.jsonl
Extract assistant text (handles multi-block):
jq -r 'select(.type == "assistant")
| [.message.content[]? | select(.type == "text") | .text]
| select(length > 0) | join("\n")' FILE.jsonl
Extract tool calls:
jq -c 'select(.type == "assistant")
| [.message.content[]? | select(.type == "tool_use")
| {tool: .name, input_keys: (.input | keys)}]
| select(length > 0)' FILE.jsonl
Session timeline:
jq -c 'select(.type == "user" or .type == "assistant")
| {ts: .timestamp, type, model: .message.model?}' FILE.jsonl
Token usage per turn:
jq -c 'select(.type == "assistant") | .message.usage
| {in: (.input_tokens + .cache_creation_input_tokens + .cache_read_input_tokens),
out: .output_tokens}' FILE.jsonl
Find sessions mentioning a term:
# Prefer: deglacer --find "term"
# Raw jq fallback:
for f in ~/.claude/projects/*/*.jsonl; do
if jq -e 'select(.type == "user" and (.message.content | type) == "string"
and (.message.content | test("term"; "i")))' "$f" >/dev/null 2>&1; then
echo "$f"
fi
done
| Don't | Why | Do instead |
|---|---|---|
jq -s '.' on JSONL | Slurps entire file into memory as array | Stream line-by-line (default jq behaviour) |
jq '.[]' on JSONL | JSONL isn't an array | Each line is already a separate object |
.role at top level | Role is at .message.role, not top-level | Use .type for entry type |
.type == "message" | No such type | Types: user, assistant, progress, system, etc. |
.type == "human" | No such type | .type == "user" + check it's not a tool result |
head -1 | jq . for discovery | Wastes a turn, first line may be queue-operation | jq -r '.type' | sort | uniq -c |
| Assume content is string | Assistant content is always array; user content varies | Check type before accessing |
2>/dev/null on everything | Hides real errors | Understand the schema, don't hedge |
| Guess at field names | 39% of jq-on-.claude commands are schema discovery | Read this reference |