Search, navigate, and resume Claude Code conversation histories. Find conversations by topic, PR, branch, worktree, skill used, files modified, repo, or recency. Use when asked to find a prior conversation, recall past work, resume interrupted sessions, or search chat history. Triggers on find conversation, prior conversation, previous session, resume session, where did we, what was I working on, find the chat, search history.
From sharednpx claudepluginhub inkeep/team-skills --plugin sharedThis skill uses the workspace's default tool permissions.
scripts/index-sessions.tsscripts/search.tsscripts/setup.shGuides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Configures VPN and dedicated connections like Direct Connect, ExpressRoute, Interconnect for secure on-premises to AWS, Azure, GCP, OCI hybrid networking.
Search and navigate Claude Code conversation histories across all projects. Two-phase: index for fast lookup, grep as fallback for deep content search.
Check if the keyword index exists:
test -f ~/.claude/session-index/index.json && echo "INDEX EXISTS" || echo "NO INDEX"
If NO INDEX (first time): run the full setup script. This builds the keyword index and optionally installs semantic search (episodic-memory). Tell the user what's happening — first-time setup takes a few minutes.
bash ~/.claude/skills/find-claude/scripts/setup.sh
If INDEX EXISTS: run an incremental update (<1s):
bun ~/.claude/skills/find-claude/scripts/index-sessions.ts
Determine which search flow applies based on the user's request:
| User says | Flow | Primary fields to search |
|---|---|---|
| "find the conversation about X" | Topic search | firstUserMessages, lastUserMessages, continuationSummaries |
| "where was I on X?" / "pick up where I left off" | Resumption | lastUserMessages (prioritized), then firstUserMessages |
| "all conversations about X" | Comprehensive | All text fields + filesModified + skills, group by project |
| "what was I working on today/yesterday?" | Recency | Filter by lastActiveAt date |
| "conversations where I used /spec" | Skill filter | skills array |
| "conversations that touched schema.ts" | File filter | filesModified array |
| "conversations about PR #2212" | PR filter | prs array |
| "what was I doing on feat/auth?" | Branch filter | branches array |
| "conversations in the worktree X" | Worktree filter | worktrees array |
| "all conversations touching doltgresql repo" | Repo filter | repos array |
| "conversations named 'auth refactor'" | Title filter | title field |
| "sessions using the code-reviewer agent" | Agent filter | agentSetting field |
Two search modes available: keyword (default) and semantic (--semantic).
Keyword search (default) — exact string match against indexed fields. The query is matched as-is (not split into individual words).
# Exact string search against index
bun ~/.claude/skills/find-claude/scripts/search.ts "figma hook"
# Filtered searches
bun ~/.claude/skills/find-claude/scripts/search.ts --skill eng:spec "openbolt credential"
bun ~/.claude/skills/find-claude/scripts/search.ts --pr 2212
bun ~/.claude/skills/find-claude/scripts/search.ts --branch feat/auth
bun ~/.claude/skills/find-claude/scripts/search.ts --today
bun ~/.claude/skills/find-claude/scripts/search.ts --file manage-schema.ts
bun ~/.claude/skills/find-claude/scripts/search.ts --worktree agents-pr2212
bun ~/.claude/skills/find-claude/scripts/search.ts --repo doltgresql
bun ~/.claude/skills/find-claude/scripts/search.ts --title "auth refactor"
bun ~/.claude/skills/find-claude/scripts/search.ts --agent code-reviewer
bun ~/.claude/skills/find-claude/scripts/search.ts --limit 30 "auth"
Semantic search (--semantic) — uses episodic-memory embeddings for natural language similarity matching. Best for fuzzy/conceptual queries where you don't know the exact words used.
bun ~/.claude/skills/find-claude/scripts/search.ts --semantic "debugging timestamp issues in doltgres"
bun ~/.claude/skills/find-claude/scripts/search.ts --semantic "figma bridge port cleanup"
Flags can be combined: --skill eng:spec --branch feat/auth "credential" requires all flag conditions AND scores text terms.
How to construct the query:
"figma hook" matches text containing that exact phrase. For broader matches, use shorter/simpler strings.--semantic): Pass a natural language description. The embedding model finds conceptually similar content even if the exact words differ.Use flags (--skill, --pr, --branch, --file, --worktree, --repo, --title, --agent, --today) for structured filters. These apply to keyword mode only.
Rules:
--semantic for fuzzy matching.| User says | Query |
|---|---|
| "find the figma bridge port cleanup hook conversation" | "figma hook" or --semantic "figma bridge port cleanup hook" |
| "the one where we were speccing out openbolts credential delegation" | --skill eng:spec "openbolt" or --semantic "openbolt credential delegation" |
| "conversations about PR 2212" | --pr 2212 |
| "what was I working on today?" | --today |
| "where we were debugging doltgres timestamp issues" | --skill eng:debug "doltgres timestamp" |
| "all conversations that touched manage-schema.ts" | --file manage-schema.ts |
| "sessions named 'auth refactor'" | --title "auth refactor" |
| "conversations using the code-reviewer agent" | --agent code-reviewer |
Retry loop — reformulate if results are weak:
If the first search returns no results or nothing that looks relevant (low scores, wrong topics):
"figma hook port cleanup" → "figma hook" → "hook"--semantic — if exact string match misses, semantic search finds conceptually similar content"auth" → "authentication", "credential", "login"Do up to 3 reformulations before falling back to grep. Each retry should change strategy, not just repeat.
How scoring works:
lastUserMessages (3x), firstUserMessages (2x), continuationSummaries (2x), structured fields (1x)The output is JSON with foundBy: ["keyword"] or foundBy: ["semantic"] indicating which engine found results. Read it and synthesize human-readable summaries per Step 4.
Dependency: Semantic search (--semantic) requires episodic-memory installed at ~/.claude/oss-repos/episodic-memory/ with npm link. Run episodic-memory sync to index new sessions.
If both keyword and semantic search return no matches (the keyword may be buried in the middle of a conversation, not captured in bookends, summaries, or embedding text):
grep -rl "KEYWORD" ~/.claude/projects/*/*.jsonl
For each hit, extract first + last user messages using head/tail and inline parsing to present context.
Compression does NOT delete messages from the JSONL file — it only affects the live context window. The full history is always on disk, so grep always searches complete content.
For every match, synthesize a human-readable summary and explain why this session matches the query. Do NOT just dump raw field values — interpret them.
For each result, produce:
### {title if available, else one-line summary synthesized from first/last messages, skills, files, and PRs}
**Why this matches:** {1-2 sentences explaining why this session is relevant to the user's query — connect the search terms to what actually happened in the session}
**Started as:** {natural language paraphrase of firstUserMessages — not raw text dump}
**Left off at:** {natural language paraphrase of lastUserMessages — what state was the work in?}
**Context:** {launchDir} | {branches} | {messageCount} messages | {compactionCount} compactions
**Agent:** {agentSetting if set} | **Skills used:** {skills}
**Created at:** {startedAt — human-readable timestamp}
**Updated at:** {lastActiveAt — human-readable timestamp}
**PRs:** {prs} | **Files modified:** {count} | **Tags:** {tags if any}
```bash
cd {launchDir} && claude -r {id}
**How to write the summary line:** Read the firstUserMessages, lastUserMessages, skills, filesModified, prs, and branches together. Synthesize a short description of what the session was *about* — not what the user literally typed, but what the work was. Examples:
- "Speccing out credential delegation for openbolts using /spec"
- "Debugging doltgres timestamp issues in a worktree (PR #2212)"
- "Researching motion graphics tools and evaluating Remotion vs alternatives"
- "Creating blog cover options for agent-in-slack post using /graphics in Figma"
**How to write the "why this matches" line:** Connect the user's search query to the specific evidence in the session. Examples:
- User searched "openbolt spec" → "This session explored openbolts credential delegation and produced a SPEC.md — the lastUserMessages show the spec was in progress when the session ended."
- User searched "PR #2212" → "PR #2212 (inkeep/agents) was the primary focus — 24 gh pr commands were issued including diff review and body edits."
**Rules:**
- `launchDir` is the directory the user must `cd` into before resuming (Claude Code is project-scoped).
- Show at most 10 results unless the user asks for more.
- If multiple sessions are clearly about the same topic (same branch, overlapping PRs, sequential dates), group them and note the relationship: "These 3 sessions appear to be continuations of the same work" with the most recent one highlighted as the best resume target.
- If a session has `compactionCount > 0`, note this — it indicates a long/complex session where significant work happened.
- If `continuationSummaries` exist, use them to enrich your summary — they capture chronological recaps of pre-compaction work and are often the richest source of what the session accomplished.
- For resumption queries ("where was I on X?"), lead with the **Left off at** state and frame the summary around what remains to be done.
## Understanding the data
### Where sessions live
`~/.claude/projects/{project-path}/{session-id}.jsonl`
The project path encodes the working directory with all non-alphanumeric characters replaced by dashes (not just `/`):
- `~/agents` → `-Users-{username}-agents`
- `~/team-skills` → `-Users-{username}-team-skills`
- `~/InkeepDev/marketing-site-v2/marketing-site` → `-Users-{username}-InkeepDev-marketing-site-v2-marketing-site`
- `~/` → `-Users-{username}`
- Paths longer than 200 characters are truncated with a hash suffix for uniqueness.
### What's in each JSONL entry
Each line is a JSON object. Common types include: user/assistant messages, `system` (including `compact_boundary` subtype), `custom-title`, `ai-title`, `tag`, `agent-setting`, `pr-link`, `file-history-snapshot`, `attribution-snapshot`, `queue-operation`, `task-summary`, and others. Message entries carry metadata: `sessionId`, `cwd`, `gitBranch`, `timestamp`, `entrypoint`, `version`.
### What the indexer extracts per session
| Field | Source | Why it matters |
|---|---|---|
| `startedAt` | First timestamp in session | When the conversation was created |
| `lastActiveAt` | Last timestamp in session | When the conversation was last active |
| `title` | `custom-title` entries (user-set, wins) or `ai-title` entries (AI-generated) | Session name — searchable via `--title` |
| `agentSetting` | `agent-setting` entries | Which agent definition was used — searchable via `--agent` |
| `tags` | `tag` entries | Searchable session tags |
| `firstUserMessages` | First 3 user messages | What the conversation started as |
| `lastUserMessages` | Last 3 user messages | Where the conversation left off (handles topic drift) |
| `branches` | `gitBranch` field on every entry | Tracks branch changes mid-session |
| `worktrees` | `git worktree add` commands | Identifies worktree-based work |
| `prs` | `pr-link` entries + PR URLs in user messages + `gh pr` commands | Links sessions to PRs |
| `repos` | `pr-link` entries + PR URLs + `--repo` flags | Which repositories were touched |
| `skills` | `Skill` tool_use entries + auto-loaded skill detection | Which skills were invoked or loaded |
| `filesModified` | `Write`/`Edit` tool_use entries | Which files were changed |
| `toolCounts` | All tool_use entries | Session character (read-heavy, write-heavy, bash-heavy) |
| `compactionCount` | `system:compact_boundary` entries | Session length/complexity indicator |
| `continuationSummaries` | "continued from previous conversation" messages | Captures pre-compaction context (the "compressed middle") |
### Compression and the "needle in the middle" problem
Claude Code compresses context when approaching limits, but compression only affects the live context window — the full message history stays in the JSONL file on disk. The indexer captures both bookends (first + last messages) and continuation summaries (which chronologically recap pre-compaction content). Between these and the grep fallback, no content is unreachable.
### Navigating compacted conversations
When a session compacts, two things appear in the JSONL:
**1. A `compact_boundary` system entry** marking where compaction happened:
```json
{
"type": "system",
"subtype": "compact_boundary",
"content": "Conversation compacted",
"compactMetadata": { "trigger": "auto", "preTokens": 167469 },
"timestamp": "2026-03-12T20:19:17.195Z"
}
2. A continuation summary as the next user message, starting with:
"This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation."
This summary is what the agent sees after compaction — it's the compressed version of everything before the boundary. The original messages are still in the JSONL above the boundary.
Shell recipes for compacted sessions:
# Count compactions in a session
grep -c 'compact_boundary' <session>.jsonl
# Find where each compaction happened (line numbers)
grep -n 'compact_boundary' <session>.jsonl
# Read the continuation summary (what the agent saw after compaction)
grep -A0 'continued from a previous conversation' <session>.jsonl | head -1 | python3 -c "
import sys, json
obj = json.loads(sys.stdin.read())
for c in obj.get('message',{}).get('content',[]):
if isinstance(c, dict): print(c.get('text','')[:1000])
"
# Read pre-compaction messages (everything before the boundary — still on disk)
# Line N = compact_boundary → lines 1 to N-1 are pre-compaction
BOUNDARY=$(grep -n 'compact_boundary' <session>.jsonl | head -1 | cut -d: -f1)
head -$((BOUNDARY - 1)) <session>.jsonl | grep '"type":"user"' | tail -5
Key insight for debugging: After compaction, the agent loses tool results, loaded skill content, and detailed earlier exchanges. If a subprocess made a bad decision late in a session, check whether the information it needed was before a compact boundary — it may have been working from the summary, not the original context.
Every Claude Code session gets a UUID (e.g., e76656ec-750d-4480-9e1c-bf57f8f9a73b) generated at session start. The ID is:
.jsonl) in ~/.claude/projects/<project>/sessionId field--output-format json output as .session_id--resume <id> and --resume <id> --fork-session to continue/fork sessions| Context | How to get it |
|---|---|
| Interactive session | Check ~/.claude/projects/<project>/ — most recently modified .jsonl file |
--output-format json subprocess | Parse jq -r '.session_id' from the JSON output |
Named session (--name) | grep -l '"customTitle":"_nest:my-label"' ~/.claude/projects/<project>/*.jsonl |
| Background task | Read the task output file, parse session_id from JSON |
| Ship review run | Check tmp/ship/local-review-runs/<run-id>/claude-output.log — parse session_id from JSON |
Worktrees get their own project key. If the main checkout is at /Users/me/myrepo and the worktree is at /Users/me/myrepo-feature, sessions go to:
~/.claude/projects/-Users-me-myrepo/~/.claude/projects/-Users-me-myrepo-feature/A claude -p subprocess's project key depends on its CWD at launch time, NOT the parent session's project key.
When something goes wrong in a subprocess (review loop, implementation iteration, nested agent), use these patterns to trace what happened.
# From a known session ID (e.g., from --output-format json or run metadata)
SESSION_ID="e76656ec-750d-4480-9e1c-bf57f8f9a73b"
find ~/.claude/projects -name "${SESSION_ID}.jsonl" 2>/dev/null
# The CWD is on every JSONL entry — check the first one
head -1 ~/.claude/projects/<project>/<session-id>.jsonl | jq -r '.cwd'
# Or check the assistant entries (which record CWD at response time)
grep '"type":"assistant"' <session>.jsonl | head -1 | jq -r '.cwd'
# All Write tool calls with file paths
grep -o '"name":"Write".*?"file_path":"[^"]*"' <session>.jsonl | grep -o '"file_path":"[^"]*"'
# All Edit tool calls
grep -o '"name":"Edit".*?"file_path":"[^"]*"' <session>.jsonl | grep -o '"file_path":"[^"]*"'
grep '"file_path":"/path/to/specific/file"' <session>.jsonl | head -3
# Count tool calls by name
grep -o '"name":"[^"]*"' <session>.jsonl | sort | uniq -c | sort -rn | head -20
grep -i '"isError":true\|"error"\|"Error"' <session>.jsonl | head -10
grep -o '"gitBranch":"[^"]*"' <session>.jsonl | sort -u
# The last line of a JSON-output session contains the result
tail -1 <session>.jsonl | jq '{result: .result, session_id: .session_id, cost: .total_cost_usd, duration_ms: .duration_ms}'
# Most recently modified sessions across all projects
ls -lt ~/.claude/projects/*/*.jsonl | head -20
# Most recent for a specific project
ls -lt ~/.claude/projects/-Users-me-myrepo/*.jsonl | head -10
# Find all sessions named with _nest: prefix
grep -rl '"customTitle":"_nest:' ~/.claude/projects/ 2>/dev/null
# Find a specific named session
grep -rl '"customTitle":"_nest:pr-review"' ~/.claude/projects/<project>/ 2>/dev/null
When /ship or /nest-claude spawns subprocesses, child sessions are separate .jsonl files. To find them:
# Children are typically in the same project dir, created around the same time
# List sessions created in the last hour for this project
find ~/.claude/projects/<project> -name "*.jsonl" -mmin -60 | sort
# Children spawned by /ship often have _nest: names
grep -l '"customTitle":"_nest:' ~/.claude/projects/<project>/*.jsonl 2>/dev/null
# Or find by the branch (children inherit the git branch)
grep -l '"gitBranch":"feat/my-feature"' ~/.claude/projects/<project>/*.jsonl 2>/dev/null
When a subprocess writes to the wrong path, trace where it thought it was:
SESSION_FILE="~/.claude/projects/<project>/<session-id>.jsonl"
# 1. What CWD did the subprocess use?
head -1 "$SESSION_FILE" | jq -r '.cwd'
# 2. What absolute paths did it write to?
grep -o '"file_path":"/[^"]*"' "$SESSION_FILE" | sort -u
# 3. What branch was it on?
grep -o '"gitBranch":"[^"]*"' "$SESSION_FILE" | head -1
# 4. Was it a worktree? (compare CWD to main checkout)
# If CWD differs from the main checkout path, it was in a worktree
When /ship review fails, the run artifacts contain the subprocess output:
# List all review runs
ls tmp/ship/local-review-runs/
# The run dir contains:
# - claude-output.log — raw JSON output from claude -p (session_id, result, cost)
# - run-metadata.txt — exit codes, timestamps, paths, branch
# - full.diff — the diff that was reviewed
# - pr-context.md — the context skill output
# Extract session ID from a run
jq -r '.session_id' tmp/ship/local-review-runs/<run-id>/claude-output.log | tail -1
# Check exit codes
grep 'claude_.*exit_code' tmp/ship/local-review-runs/<run-id>/run-metadata.txt