Skip to content
Back to skills

Claude Code Projects Jsonl Worktree Fanout

BSecurity

Search prior-session JSONL transcripts across worktree-namespaced project directories under `~/.claude/projects/`. Use when: (1) the user asks "find a previous session about X" or "remind me when we discussed Y" for a project that uses git worktrees, (2) you grep the canonical `~/.claude/projects/<project-dir>/*.jsonl` and the matching session isn't there, (3) you need to enumerate every conversation that touched a project regardless of which worktree it ran from. Claude Code stores each git ...

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 5, 2026
ai-agentspythongobashgit

Works with

  • claude code

Security analysis

B88/100
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned June 5, 2026

npx -y skills add wan-huiyan/agent-traffic-control --skill claude-code-projects-jsonl-worktree-fanout --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Claude Code Projects Jsonl Worktree Fanout?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Claude Code Projects Jsonl Worktree Fanout
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/wan-huiyan-claude-code-projects-jsonl-worktree-fanout/badge)](https://www.skillsdirectory.com/skills/wan-huiyan-claude-code-projects-jsonl-worktree-fanout)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: claude-code-projects-jsonl-worktree-fanout
description: |
  Search prior-session JSONL transcripts across worktree-namespaced project
  directories under `~/.claude/projects/`. Use when: (1) the user asks "find a
  previous session about X" or "remind me when we discussed Y" for a project
  that uses git worktrees, (2) you grep the canonical
  `~/.claude/projects/<project-dir>/*.jsonl` and the matching session isn't
  there, (3) you need to enumerate every conversation that touched a project
  regardless of which worktree it ran from. Claude Code stores each git
  worktree's session JSONLs under a separately-namespaced project directory
  (`<project-name>--claude-worktrees-<worktree-name>/`), not under the
  canonical `<project-name>/` directory. Searching only the canonical dir
  silently misses sessions run from worktrees — even though the user
  experienced them as "in the same project". Sister to the `git-worktree`
  skill family at the Claude-Code-state layer.
author: Claude Code
version: 1.0.0
date: 2026-05-28
---

# Claude Code projects/ JSONL transcripts fan out across worktrees

## Problem

The user asks: *"find that session ~2 weeks ago where we discussed X"*. You grep `~/.claude/projects/-Users-huiyanwan-Documents-foo-project/*.jsonl` for keywords, get partial or zero hits, and report "couldn't find it" — but the session **does** exist, just under a different directory because it ran inside a git worktree.

Claude Code maps **each git worktree to its own project directory** under `~/.claude/projects/`. The canonical project at `/Users/<user>/Documents/foo-project/` becomes:

```
~/.claude/projects/-Users-<user>-Documents-foo-project/
```

But a worktree created via `git worktree add .claude/worktrees/<wt-name>` becomes a separate sibling:

```
~/.claude/projects/-Users-<user>-Documents-foo-project--claude-worktrees-<wt-name>/
```

Each worktree dir holds its own JSONL transcripts for sessions started from that working directory. A project that's seen heavy worktree use (typical for `superpowers:using-git-worktrees` workflow) can have **dozens** of sibling dirs.

## Context / Trigger Conditions

- User asks about a prior session, conversation, or work in a project
- Project uses `superpowers:using-git-worktrees` or has `.claude/worktrees/` populated
- Your initial grep of `~/.claude/projects/<project-name>/*.jsonl` returned fewer hits than expected, or none
- You want to enumerate every session that ever ran against the project (e.g. for a knowledge audit)
- Symptom: `~/.claude/projects/` contains a `<project-name>` dir AND multiple `<project-name>--claude-worktrees-<wt>` siblings

## Solution

### Quick lookup (one-liner)

Glob the **wildcard suffix** to cover canonical + all worktrees:

```bash
ls ~/.claude/projects/ | grep -iE "<project-keyword>" | wc -l  # confirm fan-out
```

Then search across all of them at once:

```bash
find ~/.claude/projects/ -name "*.jsonl" -newermt "<earliest-date>" 2>/dev/null \
  | grep -iE "<project-keyword>" \
  | grep -v subagents \
  | while read f; do
      c=$(grep -ic -E "<your-search-keywords>" "$f" 2>/dev/null)
      [ "$c" -gt 0 ] && echo "$c | $(stat -f "%Sm" -t "%Y-%m-%d %H:%M" "$f") | $f"
    done | sort -rn | head -10
```

Key points:
- The `grep -iE "<project-keyword>"` filters to the project family (canonical + all worktrees)
- `grep -v subagents` excludes subagent JSONLs (under `<session-id>/subagents/agent-*.jsonl`) — those are dispatched-agent runs, not user-facing sessions
- `find -newermt` narrows by date; loosen the window if needed

### Confirming the canonical directory mapping

For a working directory at `/Users/<user>/Documents/foo-project/`, the canonical project dir is:

```
~/.claude/projects/-Users-<user>-Documents-foo-project/
```

Note: slashes in the path become dashes; the leading dash is part of the encoding. For a worktree at `/Users/<user>/Documents/foo-project/.claude/worktrees/<wt-name>/`:

```
~/.claude/projects/-Users-<user>-Documents-foo-project--claude-worktrees-<wt-name>/
```

The `--` collapsing the slash-dot-slash boundary is the giveaway that you're looking at a worktree namespace, not the canonical one.

### Reading user prompts efficiently

JSONL lines mix user messages, assistant messages, tool calls, and system reminders. To extract just **real user prompts** (skip injected skill text, tool results, system reminders):

```python
import json
with open(fn) as f:
    for line in f:
        try: d = json.loads(line)
        except: continue
        if d.get("type") != "user": continue
        c = d.get("message",{}).get("content")
        if isinstance(c, list):
            txt = " ".join(b.get("text","") for b in c if isinstance(b,dict) and b.get("type")=="text")
        elif isinstance(c, str): txt = c
        else: continue
        # Filter out injected content
        if "Base directory" in txt or "tool_use_id" in txt[:50] or txt.strip().startswith("<"):
            continue
        if not txt.strip(): continue
        print(f"--- {d.get('timestamp','')[:16]} ---")
        print(txt[:400])
```

Filters explained:
- `"Base directory"` — skill content injected by the `Skill` tool
- `"tool_use_id"` — tool-result wrappers that show up as `type: user` messages
- `txt.strip().startswith("<")` — system reminders and command-name wrappers

## Verification

The pattern is correct when:

```bash
# Should return >1 if the project uses worktrees
ls ~/.claude/projects/ | grep -iE "<project-keyword>" | wc -l
```

Returns more than 1, AND your search across all matching dirs surfaces hits that grepping the canonical dir alone missed.

## Example

Today's session: user asked *"locate a previous session (about 2 wks ago) where I ask you to help me summarize what we did and our learnings"* in the `schuh_causal_impact` project.

Initial grep of `~/.claude/projects/-Users-huiyanwan-Documents-schuh-causal-impact/*.jsonl` found 7 files, none matching the description. **Looked like the session was deleted.**

Then I expanded to:

```bash
find ~/.claude/projects/ -name "*.jsonl" -newermt "2026-05-13" ! -newermt "2026-05-16" 2>/dev/null \
  | grep -iE "schuh|causal" \
  | grep -v subagents
```

→ 7 matching JSONLs across **5 different project directories** (1 canonical + 4 worktree-namespaced). The target session was in `-Users-huiyanwan-Documents-schuh-causal-impact--claude-worktrees-eloquent-franklin-fe932f/cff8244f-2194-4448-933c-9a93d417b770.jsonl` — never would have been found by searching the canonical dir alone.

Final keyword grep across all 7 surfaced the right one in seconds (opening prompt: *"can you please review the complete commit history or those 2 repos, and help me summarise what we did..."*).

## Notes

- The fan-out can be substantial: as of this session, `~/.claude/projects/` had 38 directories matching `schuh|causal` (1 canonical + 37 worktrees). Similar fanout for other heavily-worktreed projects (`barryU` had 3+).
- **Subagent JSONLs are separate.** Within each session dir there's a `<session-id>/subagents/agent-*.jsonl` tree. These hold subagent dispatches, not user conversations — exclude them when looking for "things the user said".
- The fan-out happens automatically when a session is started with `cwd` inside a worktree path — no opt-in needed.
- After a worktree is deleted, its project directory under `~/.claude/projects/` is **not** automatically cleaned up. So historical JSONLs persist even after `git worktree remove`. This is great for archaeology, but means stale worktree dirs accumulate.
- Don't try to be clever with `~/.claude/projects/<exact-canonical>/*.jsonl` patterns — always include the wildcard suffix to cover worktree dirs.

## References

- Claude Code project state docs (CC stores per-cwd session histories under `~/.claude/projects/`)
- The `superpowers:using-git-worktrees` skill describes the worktree workflow that produces this fan-out
- Sibling pattern: `claude-code-session-jsonl-whitespace-text-block` and `claude-code-session-jsonl-orphan-advisor-tool-result` operate on the same JSONL files; this skill is the discovery layer for finding them in the first place

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.