recap: re-entry radar for Claude Code sessions
One command, full overview of recent sessions across every project on this machine. Built for the post-reboot moment: which projects, what was each session about, and the exact command to jump back in.
How to run it
Personal or project install (the skill directory is on disk next to this file):
python3 ~/.claude/skills/recap/recap.py [flags]
Plugin install (Claude Code exports the plugin root):
python3 "$CLAUDE_PLUGIN_ROOT/skills/recap/recap.py" [flags]
Nothing to install beyond Python 3 (stdlib only, no third-party packages).
| Flag |
Meaning |
| (none) |
last 15 sessions, newest first, instant, no network |
--since 7d |
only sessions active in the window (30m, 24h, 7d, 2w) |
--project foo |
filter by substring of the absolute project path; separators are interchangeable (my_app, my-app, work/my_app) |
--grep PATTERN |
only sessions whose conversation matches PATTERN (regex, case-insensitive); the matching excerpt replaces the ↳ line |
--dirty |
annotate each row with its project's uncommitted and unpushed counts (read-only git calls) |
--limit N |
max rows (default 15) |
--json |
machine-readable output with full session ids and resume commands |
--no-tail |
hide the ↳ line that says where each session stopped |
--smart |
real one-sentence summaries via ONE claude -p haiku call (network, ~10s) |
--pick |
choose a row interactively, then cd + claude -r into it directly |
--open |
open EVERY listed session in its own new terminal tab and resume it there |
--claude-flags "..." |
extra flags for each resumed claude (e.g. "--chrome --dangerously-skip-permissions"); also shown in the printed resume lines and --json |
--terminal NAME |
which terminal --open drives: iTerm2, Terminal, tmux, wezterm, kitty, gnome-terminal, konsole (default: auto-detected) |
--yes / -y |
skip the --open confirmation prompt (required when stdin is not a TTY) |
--dry-run |
with --open: print the tabs and commands, open nothing |
--color |
force ANSI colors when output is piped (e.g. inside Claude Code) |
--plain |
no ANSI at all (also honors NO_COLOR / FORCE_COLOR env) |
Usage examples
# after a reboot: what was I doing?
python3 ~/.claude/skills/recap/recap.py
# everything in one repo this week
python3 ~/.claude/skills/recap/recap.py --since 7d --project my-repo
# which session did I solve this in?
python3 ~/.claude/skills/recap/recap.py --grep "connection pool" --limit 5
# where did I leave work uncommitted?
python3 ~/.claude/skills/recap/recap.py --dirty --since 7d
# jump straight back into a session
python3 ~/.claude/skills/recap/recap.py --pick
# re-open yesterday's whole working set in terminal tabs, browser enabled, no permission prompts
python3 ~/.claude/skills/recap/recap.py --since 24h --limit 12 \
--open --yes --claude-flags "--chrome --dangerously-skip-permissions"
# preview first, open nothing
python3 ~/.claude/skills/recap/recap.py --open --dry-run
# feed into other tooling
python3 ~/.claude/skills/recap/recap.py --json --limit 50
The --open flag
Restores a whole working set at once. It opens one new tab per listed session and types the resume command into it. Rules:
- The session recap itself runs in is skipped automatically (matched on
CLAUDE_CODE_SESSION_ID), so it never re-opens itself.
- Sessions whose project directory no longer exists are skipped and reported, never opened.
- Scope comes from the normal filters, so
--limit / --since / --project decide exactly which tabs appear. Check with --dry-run before committing.
- Without
--yes it asks for confirmation; with piped stdin (Claude Code's Bash tool) it refuses to open unless --yes is passed.
--claude-flags is passed through verbatim to every tab. --dangerously-skip-permissions disables all permission checks in each opened session; the script prints a warning before doing it.
- The terminal is auto-detected: tmux when running inside it (even on macOS), else iTerm2/Terminal on macOS via
osascript, else wezterm, kitty, gnome-terminal or konsole. --terminal NAME forces one. Where none can be driven, --open says so and opens nothing; --open --dry-run needs no terminal and still prints the commands.
- Every opened tab outlives
claude: quitting a resumed session leaves a shell in that project instead of closing the restored window.
When Claude runs this skill
Run the script with Bash, ALWAYS with --color (tool output is piped, auto-detection would strip the colors; Claude Code renders ANSI). Add --since/--project if the user narrowed the question, then relay the table. Quote the resume command (cd <path> && claude -r <id>) for any session the user wants to re-enter. Only add --smart if the user explicitly asks for better summaries; it makes a network call.
Reach for --grep when the user is looking for a session by what happened in it ("where did I fix the connection pool"), not by when it ran. Reach for --dirty when the question is about unfinished work ("what did I leave half-done", "where do I still have uncommitted changes"); it runs read-only git commands, so it is safe but not instant.
Use --open only when the user explicitly asks to re-open or resume the sessions. Because the Bash tool pipes stdin, --open needs --yes; treat the user's request as the confirmation, and set the scope with --limit/--since so no unwanted session gets a tab. Pass --claude-flags only with flags the user named; state the permission-bypass warning in your reply whenever --dangerously-skip-permissions is among them.
Data sources and guarantees
~/.claude/history.jsonl is the fast index (prompt text, timestamp, project path, session id). CLAUDE_CONFIG_DIR is honored when set.
~/.claude/projects/<encoded>/<session>.jsonl transcripts are parsed only for the displayed rows (title, branch, model, turn count). Project paths always come from the cwd/project fields, never decoded from folder names (that encoding is lossy). Because claude -r only finds a session from the directory it started in, recap resolves the path to the first recorded cwd whose encoding matches the transcript's folder, so a mid-session cd never produces an unresumable command; a session with no cwd at all borrows one from a sibling transcript.
- Summary preference: Claude Code's own
ai-title line, else the first real user prompt, else the first slash command the session ran, else (no prompt). A transcript's user role also holds what Claude Code injects into it, and none of it counts as a prompt: command blocks, local command output, the body of an invoked skill, hook text, task notifications, ! bash runs and interrupt markers.
- The
↳ line is where the session stopped: Claude's last reply when the run finished, the unanswered prompt when it was interrupted, whichever came last. --json always carries it as lastMessage / lastSpeaker, so --no-tail only quiets the table.
--grep searches the conversation. A transcript is rejected by a raw byte scan before it is parsed, using a word every match must contain, derived from the pattern only when that is provably safe (never across an alternation, a quantifier or whitespace).
--dirty runs git status --porcelain and one rev-list per project directory, cached and only for the rows actually shown. Not-a-repository, no upstream and no git installed all yield the same non-answer: no counts on the row.
--smart privacy: the only path that leaves the machine. It shells out to the local claude CLI once and sends, for the listed sessions only, each session's 8-character id prefix, its title (first 150 characters), and its first user prompt (first 300 characters). No file bodies, no transcript contents, no other sessions. If claude is not on PATH, --smart is skipped with a warning and the offline summaries are used.
- Turn count groups assistant streaming chunks by message id and is labeled approximate.
- Broken or partial JSONL lines are skipped, never fatal. Times shown in the local timezone.
- Display: day-grouped timeline (Today green, Yesterday amber), 14-day activity sparkline in the header, per-project colored dot (stable hash), four-level gray hierarchy with one accent color, per-session resume line indented with spaces only (safe to copy), and a small footer with a GitHub-star CTA and author link (clickable OSC 8 hyperlinks on supporting terminals). Falls back to plain text when piped or when NO_COLOR is set.
- Read-only: the tool never writes or deletes anything. It sees only sessions still on disk (Claude Code prunes old ones).
--open, --pick and --dirty are the only side-effecting paths: --open drives a terminal, --pick execs claude -r in the chosen directory, --dirty runs read-only git commands in the project directories. None of them writes or touches session data.
Optional shell alias
alias recap='python3 ~/.claude/skills/recap/recap.py'
1---2name: recap3description: Show recent Claude Code sessions across all projects, so the user can re-enter work after a reboot or context switch. Lists per session the absolute project path, a short summary, where the session stopped, last activity, turn count, git branch, model, and a ready-to-paste resume command; can search sessions by what was said in them, flag projects with uncommitted or unpushed work, and re-open every session at once in new terminal tabs. Use when the user asks "what was I working on", "which projects did I touch recently", "where did I leave off", "list my recent sessions", "how do I get back into that session", "open all my sessions again", "which session did I fix X in", "where do I still have uncommitted work", or invokes /recap. Reads only local files; the default run is instant and offline.4license: MIT5---67# recap: re-entry radar for Claude Code sessions89One command, full overview of recent sessions across every project on this machine. Built for the post-reboot moment: which projects, what was each session about, and the exact command to jump back in.1011## How to run it1213Personal or project install (the skill directory is on disk next to this file):1415```bash16python3 ~/.claude/skills/recap/recap.py [flags]17```1819Plugin install (Claude Code exports the plugin root):2021```bash22python3 "$CLAUDE_PLUGIN_ROOT/skills/recap/recap.py" [flags]23```2425Nothing to install beyond Python 3 (stdlib only, no third-party packages).2627| Flag | Meaning |28|------|---------|29| (none) | last 15 sessions, newest first, instant, no network |30| `--since 7d` | only sessions active in the window (`30m`, `24h`, `7d`, `2w`) |31| `--project foo` | filter by substring of the absolute project path; separators are interchangeable (`my_app`, `my-app`, `work/my_app`) |32| `--grep PATTERN` | only sessions whose conversation matches PATTERN (regex, case-insensitive); the matching excerpt replaces the `↳` line |33| `--dirty` | annotate each row with its project's uncommitted and unpushed counts (read-only git calls) |34| `--limit N` | max rows (default 15) |35| `--json` | machine-readable output with full session ids and resume commands |36| `--no-tail` | hide the `↳` line that says where each session stopped |37| `--smart` | real one-sentence summaries via ONE `claude -p` haiku call (network, ~10s) |38| `--pick` | choose a row interactively, then `cd` + `claude -r` into it directly |39| `--open` | open EVERY listed session in its own new terminal tab and resume it there |40| `--claude-flags "..."` | extra flags for each resumed `claude` (e.g. `"--chrome --dangerously-skip-permissions"`); also shown in the printed resume lines and `--json` |41| `--terminal NAME` | which terminal `--open` drives: `iTerm2`, `Terminal`, `tmux`, `wezterm`, `kitty`, `gnome-terminal`, `konsole` (default: auto-detected) |42| `--yes` / `-y` | skip the `--open` confirmation prompt (required when stdin is not a TTY) |43| `--dry-run` | with `--open`: print the tabs and commands, open nothing |44| `--color` | force ANSI colors when output is piped (e.g. inside Claude Code) |45| `--plain` | no ANSI at all (also honors NO_COLOR / FORCE_COLOR env) |4647## Usage examples4849```bash50# after a reboot: what was I doing?51python3 ~/.claude/skills/recap/recap.py5253# everything in one repo this week54python3 ~/.claude/skills/recap/recap.py --since 7d --project my-repo5556# which session did I solve this in?57python3 ~/.claude/skills/recap/recap.py --grep "connection pool" --limit 55859# where did I leave work uncommitted?60python3 ~/.claude/skills/recap/recap.py --dirty --since 7d6162# jump straight back into a session63python3 ~/.claude/skills/recap/recap.py --pick6465# re-open yesterday's whole working set in terminal tabs, browser enabled, no permission prompts66python3 ~/.claude/skills/recap/recap.py --since 24h --limit 12 \67 --open --yes --claude-flags "--chrome --dangerously-skip-permissions"6869# preview first, open nothing70python3 ~/.claude/skills/recap/recap.py --open --dry-run7172# feed into other tooling73python3 ~/.claude/skills/recap/recap.py --json --limit 5074```7576## The `--open` flag7778Restores a whole working set at once. It opens one new tab per listed session and types the resume command into it. Rules:7980- The session recap itself runs in is skipped automatically (matched on `CLAUDE_CODE_SESSION_ID`), so it never re-opens itself.81- Sessions whose project directory no longer exists are skipped and reported, never opened.82- Scope comes from the normal filters, so `--limit` / `--since` / `--project` decide exactly which tabs appear. Check with `--dry-run` before committing.83- Without `--yes` it asks for confirmation; with piped stdin (Claude Code's Bash tool) it refuses to open unless `--yes` is passed.84- `--claude-flags` is passed through verbatim to every tab. `--dangerously-skip-permissions` disables all permission checks in each opened session; the script prints a warning before doing it.85- The terminal is auto-detected: tmux when running inside it (even on macOS), else iTerm2/Terminal on macOS via `osascript`, else wezterm, kitty, gnome-terminal or konsole. `--terminal NAME` forces one. Where none can be driven, `--open` says so and opens nothing; `--open --dry-run` needs no terminal and still prints the commands.86- Every opened tab outlives `claude`: quitting a resumed session leaves a shell in that project instead of closing the restored window.8788## When Claude runs this skill8990Run the script with Bash, ALWAYS with `--color` (tool output is piped, auto-detection would strip the colors; Claude Code renders ANSI). Add `--since`/`--project` if the user narrowed the question, then relay the table. Quote the resume command (`cd <path> && claude -r <id>`) for any session the user wants to re-enter. Only add `--smart` if the user explicitly asks for better summaries; it makes a network call.9192Reach for `--grep` when the user is looking for a session by what happened in it ("where did I fix the connection pool"), not by when it ran. Reach for `--dirty` when the question is about unfinished work ("what did I leave half-done", "where do I still have uncommitted changes"); it runs read-only git commands, so it is safe but not instant.9394Use `--open` only when the user explicitly asks to re-open or resume the sessions. Because the Bash tool pipes stdin, `--open` needs `--yes`; treat the user's request as the confirmation, and set the scope with `--limit`/`--since` so no unwanted session gets a tab. Pass `--claude-flags` only with flags the user named; state the permission-bypass warning in your reply whenever `--dangerously-skip-permissions` is among them.9596## Data sources and guarantees9798- `~/.claude/history.jsonl` is the fast index (prompt text, timestamp, project path, session id). `CLAUDE_CONFIG_DIR` is honored when set.99- `~/.claude/projects/<encoded>/<session>.jsonl` transcripts are parsed only for the displayed rows (title, branch, model, turn count). Project paths always come from the `cwd`/`project` fields, never decoded from folder names (that encoding is lossy). Because `claude -r` only finds a session from the directory it started in, recap resolves the path to the first recorded `cwd` whose encoding matches the transcript's folder, so a mid-session `cd` never produces an unresumable command; a session with no `cwd` at all borrows one from a sibling transcript.100- Summary preference: Claude Code's own `ai-title` line, else the first real user prompt, else the first slash command the session ran, else `(no prompt)`. A transcript's user role also holds what Claude Code injects into it, and none of it counts as a prompt: command blocks, local command output, the body of an invoked skill, hook text, task notifications, `!` bash runs and interrupt markers.101- The `↳` line is where the session stopped: Claude's last reply when the run finished, the unanswered prompt when it was interrupted, whichever came last. `--json` always carries it as `lastMessage` / `lastSpeaker`, so `--no-tail` only quiets the table.102- `--grep` searches the conversation. A transcript is rejected by a raw byte scan before it is parsed, using a word every match must contain, derived from the pattern only when that is provably safe (never across an alternation, a quantifier or whitespace).103- `--dirty` runs `git status --porcelain` and one `rev-list` per project directory, cached and only for the rows actually shown. Not-a-repository, no upstream and no git installed all yield the same non-answer: no counts on the row.104- `--smart` privacy: the only path that leaves the machine. It shells out to the local `claude` CLI once and sends, for the listed sessions only, each session's 8-character id prefix, its title (first 150 characters), and its first user prompt (first 300 characters). No file bodies, no transcript contents, no other sessions. If `claude` is not on PATH, `--smart` is skipped with a warning and the offline summaries are used.105- Turn count groups assistant streaming chunks by message id and is labeled approximate.106- Broken or partial JSONL lines are skipped, never fatal. Times shown in the local timezone.107- Display: day-grouped timeline (Today green, Yesterday amber), 14-day activity sparkline in the header, per-project colored dot (stable hash), four-level gray hierarchy with one accent color, per-session resume line indented with spaces only (safe to copy), and a small footer with a GitHub-star CTA and author link (clickable OSC 8 hyperlinks on supporting terminals). Falls back to plain text when piped or when NO_COLOR is set.108- Read-only: the tool never writes or deletes anything. It sees only sessions still on disk (Claude Code prunes old ones). `--open`, `--pick` and `--dirty` are the only side-effecting paths: `--open` drives a terminal, `--pick` `exec`s `claude -r` in the chosen directory, `--dirty` runs read-only git commands in the project directories. None of them writes or touches session data.109110## Optional shell alias111112```bash113alias recap='python3 ~/.claude/skills/recap/recap.py'114```