| name | chat-archeologist |
| description | Use when the user asks "what did we decide about X", "find the session where we discussed Y", "when did we first hit Z bug", "what was the rationale behind W", or any question that needs answers retrieved from prior Claude Code sessions across all projects. Also use proactively when the current session needs context that predates the loaded conversation thread — chat sessions are a queryable backup to long-term memory, and they hold reasoning traces (rejected alternatives, partial understandings, the why) that curated MEMORY.md / project_*.md entries don't capture. Searches the global ~/.claude/projects/*/*.jsonl archive via ripgrep + Python turn-pair extraction. NOT for current state queries (pod status, build status — read those directly), NOT for future plans (read paper §14 work queue or CLAUDE.md), NOT for questions already answered by MEMORY.md (check that first). |
chat-archeologist — retrieve context from prior Claude Code sessions
Sessions persist as JSONL files at ~/.claude/projects/<project-slug>/<session-uuid>.jsonl. Each line is one event: user prompts, assistant responses (text + thinking blocks), tool calls, tool results, hook injections. A large local session archive (files range from kilobytes to hundreds of MB).
Two layers of memory exist locally:
| Layer | What it holds | When to read |
|---|
~/.claude/projects/<project>/memory/MEMORY.md + project_*.md | Curated outcomes, decisions, validated findings | First — faster, more reliable |
~/.claude/projects/<project>/<uuid>.jsonl | Reasoning traces, rejected alternatives, partial understandings | Fall back here when the curated layer doesn't cover the question |
This skill drives the second layer.
Helpers (already installed)
~/.claude/scripts/chat-search.sh "<query>" [max_per_file=3] [total_cap=30]
Ripgrep across all session JSONLs, sorted by file mtime descending (most recent first). Returns <filepath>:<lineno>:<snippet> lines. Search is fixed-string + case-insensitive by default.
~/.claude/scripts/chat-search.sh "p95 latency regression" 3 10
~/.claude/scripts/chat-search.sh "match_named_modules"
~/.claude/scripts/chat-extract.py <filepath> <lineno> [--max-chars 5000]
Extracts the user/assistant turn pair around a matched line as readable markdown. Walks backward from the match to find the most recent user message with text content (not tool_result placeholders), walks forward to find the next assistant message with text content (not tool_use placeholders). Skips thinking blocks. UTF-8-safe on Windows.
python ~/.claude/scripts/chat-extract.py "/c/Users/you/.claude/projects/<project>/<uuid>.jsonl" 8076 --max-chars 1500
Workflow (every retrieval)
-
Parse the question. Identify 2-4 distinctive keyword candidates. Prefer specific tokens (function names, error messages, paper section numbers, exact figures like "420ms", "3.2x", commit hashes) over generic terms ("the result", "the issue").
-
Search. Run chat-search.sh with your strongest keyword first. Cap at 4 search calls — past 4 you're hunting blind. If 0 hits, try a different keyword. If >30 hits, narrow with a more distinctive phrase.
-
Triage. Most recent + highest-keyword-density hits go to the top of the read queue. Aim for 3-6 turn pairs total across the synthesis.
-
Extract. For each priority hit, run chat-extract.py <filepath> <lineno>. Read the user prompt + assistant response.
-
Synthesize. Answer the question with concrete content from the extracted turns. Cite each load-bearing claim as <session-uuid-prefix-8>:L<lineno> so the source can be re-located via ls ~/.claude/projects/*/<prefix>*.
Hard rules
- Never fabricate content from sessions you didn't actually extract. If search returns 0 hits, say so — don't invent a plausible answer.
- Don't read full JSONLs. Some are 220 MB. Always go through
chat-extract.py which streams + bounds output.
- Quote load-bearing numbers exactly. Don't paraphrase "p95 420ms vs 180ms baseline" into "roughly 2x slower." Numbers are where paraphrasing introduces errors.
- Skip thinking blocks in citations. The Python helper strips them; if raw thinking content surfaces anyway, don't quote it.
- Check curated memory first. If MEMORY.md or a project_*.md entry already has the answer, use that — it's faster and authoritative. Only fall back to chat archeology when the curated layer doesn't cover the question.
Citation format
- Good: "The retry timeout was bumped from 5s to 30s after the staging 504s in session
11bce930:L47288" — has session prefix + line, allows re-verification.
- Bad: "I think we discussed this somewhere in May" — no citation, useless to verify.
- Bad: "The user said the result was a failure" — no quote, no line, ungroundable.
The 8-character session UUID prefix is enough to re-locate the file. Different sessions in the same project will have different prefixes — they don't collide.
When to refuse / suggest a different path
- Question is about current state (running pods, in-flight build, current branch) → read state directly, don't search history.
- Question is about future plans → read paper §14 work queue or recent CLAUDE.md status block.
- Question is so vague no keywords distinguish it ("what have we done") → ask the requester for a specific decision, error, finding, or date range.
- Question is about a curated decision likely in MEMORY.md → grep that first; chat archeology is the backup, not the primary.
Output format
Lead with a 1-2 sentence direct answer if your extraction supports one. Then:
Citations:
<session-prefix>:L<lineno> — what was said at that turn (1 sentence summary)
- (repeat for up to 4 most load-bearing sources)
What you didn't find (if relevant): brief note on what the search couldn't surface, so the requester knows where retrieval stopped being reliable.
If multiple sessions disagree (e.g., one declared a result ALIVE, a later one retracted it), note both and order by mtime — the latest session is usually the current truth, but only after verifying the framing actually evolved (vs the older session being a strawman).
Promotion path back to memory
When you surface a load-bearing finding from chat archeology that isn't yet captured in MEMORY.md or any project_*.md entry, flag the gap:
"This came from chat archeology, not durable memory — worth a one-line MEMORY.md entry pointing to a new project_*.md so future-you doesn't have to re-search this."
Don't write the file yourself unless asked. Just flag the gap. The point of this skill is to make the chat archive a queryable layer of memory, not to replace the curated layer.
Subagent vs skill — when to use which
| Path | Tool | When |
|---|
| Skill (this file) | Direct helper invocation in current session | Fast lookup mid-conversation; current session needs the data inline to continue reasoning |
Subagent (~/.claude/agents/chat-archeologist.md) | Agent({ subagent_type: "chat-archeologist", ... }) | Fresh-context dispatch; the synthesized answer IS the deliverable; current session shouldn't be polluted with raw search output |
Pick the subagent when retrieval-and-synthesis is the entire task. Pick this skill when the current session needs prior context to keep reasoning forward.
UX limitation worth knowing
The default snippet from chat-search.sh is 120 chars of raw JSONL, which often shows JSON metadata (parentUuid, promptId, etc.) before the actual content starts. Triaging 30 hits by snippet alone is slow. Workaround: extract the top 3-5 hits regardless of snippet quality and let chat-extract.py show you the actual turn — the snippet is for ordering, the extract is for reading.
A v2 of the search script could pre-extract the text field via Python so snippets are prose, not JSON. ~10 LOC change. Not blocking.