Skip to content

rmdes/claude-recent

Repository files navigation

claude-recent

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.

When this is useful

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

How it works

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.

Layout

<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

Configuration

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-sessions

Precedence (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.

Install

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 recommended

The installer:

  1. Creates .env from .env.example if missing
  2. Creates the data dir (per $CLAUDE_SESSIONS_DIR, env wins over .env)
  3. Marks the scripts executable
  4. Backs up ~/.claude/settings.json and merges the three hooks (de-duping any prior log-event.sh entries)
  5. Symlinks claude-recent into ~/.local/bin/
  6. 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:

  1. ~/.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.
  2. ~/.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.
  3. ~/.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.

Usage

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)

Plain list

   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.

Interactive picker

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).

What gets logged

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.

Limitations

  • 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-running backfill.py later picks up summaries for past sessions.
  • Per-host hooks and history. You must run install.sh AND backfill.py on 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.

Uninstall

# 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

Troubleshooting

  • 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. Run install.sh again and start a fresh claude session.
  • Concurrent writers from multiple hosts: handled with flock. If you see garbled lines, check flock is on $PATH (util-linux package on Debian/ Ubuntu) and that the shared filesystem supports advisory locks.
  • Picker shows nothing / exits silently: fzf is missing (apt install fzf) or your fzf is older than 0.36 (--with-nth=2.. syntax requires a recent build).

About

Cross-machine session tracker for Claude Code — index sessions to a shared folder, browse with an fzf TUI, resume from anywhere

Topics

Resources

Stars

Watchers

Forks

Contributors