Cross-machine session tracker for Claude Code.
Index your sessions to a shared folder, browse them with an fzf TUI, and
resume any of them from anywhere.
Anywhere you run Claude Code on more than one machine (or distro, or container) that all share a writable directory:
- Two WSL distros sharing
/mnt/wsl/dev/— the original use case. WSL crashes are common and tab loss costs context. - Local laptop + a remote dev box on a sshfs/NFS mount.
- Multiple containers writing to a shared volume.
- A single machine where you just want a unified, persistent log of every Claude session — even one host benefits from the searchable history.
What you stop losing:
- which directory each session was running from
- the session ID needed for
claude --resume - which of the last N sessions was the one you actually care about
A tiny hook fires on SessionStart, UserPromptSubmit, and Stop, appending
one JSON line per event to a shared index file. All participating Claude
installs append to the same index (with flock for safety). A query CLI
aggregates the events per session and prints recent sessions with a
copy-pasteable resume command, or opens an interactive picker.
The default shared path is /mnt/wsl/dev/.claude-sessions/ (the WSL case).
Override with $CLAUDE_SESSIONS_DIR for any other shared directory.
<repo>/ # the code, somewhere both machines can read
├── log-event.sh # hook handler (writes to the index)
├── claude-recent # query CLI / fzf picker
├── preview-session.sh # fzf preview pane (used by --pick)
├── backfill.py # one-shot historical seeder
├── install.sh # idempotent installer
├── _loadenv.sh # .env loader (sourced by bash scripts)
├── .env.example # config template — copy to .env to edit
├── README.md
└── CLAUDE.md # install instructions for an AI agent
$CLAUDE_SESSIONS_DIR/ # runtime data — must be writable from
├── index.jsonl # every host you want to track
└── .lock
install.sh creates a local .env from .env.example if missing. Edit
.env to change defaults — point the data dir at an NFS mount, an sshfs
path, a container volume, or wherever your hosts have shared write access:
# .env
CLAUDE_SESSIONS_DIR=/mnt/share/claude-sessionsPrecedence (highest first): real environment variables → .env file →
hardcoded fallback (/mnt/wsl/dev/.claude-sessions/). So you can override
per-shell with export CLAUDE_SESSIONS_DIR=... without touching the file.
.env is gitignored — it's per-host config, not source.
On each machine / distro / container you want to track, clone or otherwise make this repo readable, then:
bash <repo>/install.sh
python3 <repo>/backfill.py # optional but recommendedThe installer:
- Creates
.envfrom.env.exampleif missing - Creates the data dir (per
$CLAUDE_SESSIONS_DIR, env wins over.env) - Marks the scripts executable
- Backs up
~/.claude/settings.jsonand merges the three hooks (de-duping any priorlog-event.shentries) - Symlinks
claude-recentinto~/.local/bin/ - Smoke-tests the hook end-to-end and removes the test record
backfill.py seeds the index with sessions that pre-date the hook,
reading three sources in order:
~/.claude/projects/*/sessions-index.json— when present, has the friendly auto-generated summary Claude writes per session ("Rybbit deployment: Docker migration, ..."), the first prompt, and the canonical cwd. Only a fraction of projects have one.~/.claude/projects/*/<sessionId>.jsonl— the raw transcripts. Walked directly to extract cwd and the first non-sidechain, non-meta user prompt. This is where the bulk of history actually lives.~/.claude/sessions.db— fallback for old sessions whose JSONL is no longer on disk (e.g. from a prior WSL install). Yields cwd + last_activity + model, but no title.
It filters out sub-agent transcripts (<project>/<sessionId>/subagents/agent-*.jsonl),
sidechain entries, and sessions whose "first prompt" is actually a system
meta-prompt template (<command-name>, <system-reminder>,
<local-command-caveat>, summarizer wrappers, etc.).
Idempotent: re-running only adds session IDs that aren't already in the index.
Both scripts are idempotent — re-run either any time.
claude-recent # last 20 sessions, sorted by recency
claude-recent 50 # last 50
claude-recent --pick # interactive picker (fzf)
claude-recent --here # only sessions whose cwd is under $PWD
claude-recent --distro Ubuntu
claude-recent --days 7 # only sessions active in the last 7 days
claude-recent --raw # JSON, one session per line (for scripting) 3m ago [Ubuntu ] 3f5b37e7 ( 12 ev) fix the helm chart for tyk-cleanup prd
cd /mnt/wsl/dev/openshift-central/tyk && claude --resume 3f5b37e7-...
The second line is copy-pasteable.
eval "$(claude-recent --pick)"Opens an fzf picker. Type to filter (matches title or cwd), arrow keys to
navigate, Enter to print the resume command, Esc to cancel. The right
pane previews session details (summary, first prompt, model, event timeline,
resume command).
If you don't want to eval, just run claude-recent --pick and copy the
printed command yourself.
Requires fzf (apt install fzf).
Hook (live) records per event:
ts, distro, host, user, ppid, event, session_id, cwd, source, transcript, prompt(first 200 chars). Prompts are truncated; only the first prompt of a
session becomes the displayed title.
Backfill records add: summary (when sourced from sessions-index.json),
model, message_count. The query CLI prefers summary over prompt when
showing a title, so freshly-logged sessions get nicer names later when Claude
writes a summary and you re-run backfill.py.
- Friendly titles come from
sessions-index.json(Claude auto-generates a summary per session, e.g. "Rybbit deployment: Docker migration, ..."). Live sessions logged via the hook only show the first user prompt — the summary is added by Claude after-the-fact. Re-runningbackfill.pylater picks up summaries for past sessions. - Per-host hooks and history. You must run
install.shANDbackfill.pyon every host that should contribute to the index. The shared index file unifies them, but each host's~/.claude/is separate. - Append-only log. No automatic rotation. The file is small (~200 bytes per event) but truncate it manually if it ever bothers you.
- DB sessions without local JSONL (e.g. from a prior WSL install) get backfilled with cwd + last_activity but no title.
# Remove hooks (manual)
$EDITOR ~/.claude/settings.json # delete the three log-event.sh blocks
rm ~/.local/bin/claude-recent
# Optionally:
rm -rf /mnt/wsl/dev/.claude-sessions- Empty list after starting a session: the current shell session is logged
only after its next
SessionStart/prompt. Hooks don't fire retroactively. No session index yet: hook hasn't fired. Runinstall.shagain and start a freshclaudesession.- Concurrent writers from multiple hosts: handled with
flock. If you see garbled lines, checkflockis on$PATH(util-linuxpackage on Debian/ Ubuntu) and that the shared filesystem supports advisory locks. - Picker shows nothing / exits silently:
fzfis missing (apt install fzf) or yourfzfis older than 0.36 (--with-nth=2..syntax requires a recent build).