ccr is a small CLI tool written in Go that lets you resume a
Claude Code session from any directory.
It shows an interactive picker of your past sessions, then cds into the
selected session's original working directory and replaces itself via
exec with claude --resume <session_id>.
Session files are looked up under the CLAUDE_CONFIG_DIR environment
variable, or $HOME/.claude if it isn't set.
By default only sessions for the current directory are listed; pass -g
to also list sessions from other directories.
ai-title and last-prompt information from the session file is shown
as well.
Typing v in the list renders the session as HTML and opens it in your
web browser.
brew tap yteraoka/cask
brew trust yteraoka/cask
brew install --cask ccrmise use -g github:yteraoka/ccr@0.0.1go install github.com/yteraoka/ccr/cmd/ccr@latestOr build from a checkout:
go build -o ccr ./cmd/ccrccrOR
ccr -gBy default, only sessions belonging to the current directory are targeted
(matched the same way Claude Code encodes ${CLAUDE_CONFIG_DIR}/projects/<dir>
— every character outside a-zA-Z0-9 in the cwd becomes -). Adding -g
targets sessions from every project instead.
On a machine with no browser — a server you are working on over SSH,
typically — add -n (-no-browser) so that v only prints the transcript
URL instead of trying to open it. CCR_NO_BROWSER=1 does the same without
the flag, and ccr behaves that way on its own whenever there is no way to
open a browser at all. See Viewing a full transcript (v).
The terminal splits into two panes:
- Top pane — sessions sorted by recency (most recently active first),
with columns
TIMESTAMP(local time),SESSION ID,PID(only shown for sessions with a currently runningclaudeprocess — see below),TOKENS(cumulative token usage across the session's assistant messages), andCWD(basename). On a terminal too narrow to fit both (under 95 columns), the session id is shortened to its first 8 characters soCWDstays readable. The bottom line of this pane shows the available keys. - Bottom pane — a live preview of the highlighted session: its full session id (even when the list above shortens it), directory, title (if Claude Code has generated one), file size, token usage broken down by kind (in / out / cache write / cache read) behind the total, start/end time, and the last few prompts you sent in that session.
| Key | Action |
|---|---|
↑/k/p, ↓/j/n |
Move the cursor |
Space/PageDown, b/Backspace/PageUp |
Page through the list |
g, G |
Jump to the first or last session |
/ |
Filter the list as you type (id and directory) |
Enter |
Resume the selected session (cd + exec claude --resume <id>) |
i |
Inspect the selected session's raw jsonl without leaving the terminal |
v |
View the full transcript of the selected session in your browser (see below) |
q, Esc, Ctrl-C |
Quit without doing anything |
CLAUDE_CONFIG_DIR— where Claude Code stores its data. Defaults to${HOME}/.claude.BROWSER— the command used to open the transcript viewer (see below). Follows the common convention: if any word contains%s, the URL is substituted there; otherwise the URL is appended as the last argument. IfBROWSERis unset, macOS falls back to opening the URL withopen; on other platforms there is nothing to fall back to, sovshows the URL instead of opening it.CCR_NO_BROWSER— set it to1(anything but0,false,no, oroff) to makevshow the transcript URL instead of opening a browser, the same as passing-n.- If a
.envrcfile exists in the destination directory, it is loaded viadirenv exec.
Pressing i opens the selected session's file in a full-screen viewer,
without leaving the terminal. It lists one row per line — the line number
in the file, its type, its timestamp, and the start of the raw text —
n and p step a line, Space pages the list down, b and Backspace
page it up, / searches it incrementally, and i (or Enter) opens the
line under the cursor as pretty-printed JSON, in a modal floating over the
list.
Filtering works the same on both lists: type and only the matching rows
stay, with the prompt showing how many of how many. Enter ends the
typing but keeps the list narrowed, so the ordinary keys then move,
resume and open within it; Esc clears the filter, and only then does it
leave the screen. On the session list a query matches the id and the
directory; in the file it matches the whole raw line, so tool_use or a
path finds the lines carrying it. Enter only means resume on the
picker's own list, so it is free here.
Inside the modal, n and p step to the next and previous line and show
it straight away, so you can walk the file without closing and reopening
it at every line; the list behind follows along. Every movement key means
the same on both screens: n/p step a line, Space pages down, and b
and Backspace page up. The list stays visible behind it,
dimmed, so opening a line never loses your place in the file. The modal
wraps to its width and scrolls. Lines that are not valid JSON are listed
too and shown as they are: seeing them is the point of a raw preview.
q/Esc steps back out, first closing the modal and then the viewer.
Pressing v on a session renders its entire jsonl transcript as a
self-contained, light-themed HTML page — Human and AI turns are visually
distinguished, prose is rendered as Markdown, and commands/diffs/file
content are syntax-highlighted. Notifications the harness injects on your
behalf (a sub agent reporting its result, a monitor event) arrive as user
lines but are labelled 🔔 Notification rather than attributed to you.
Runs of back-to-back tool calls are folded into a single collapsed
N tool calls block, with each call collapsed again inside it and
individually expandable — its summary names the tool and what it acted on,
so the transcript reads as a conversation until you open one. A failed call
is coloured on that summary line, so you can spot it without opening
anything. A turn that also says something keeps its own card and ends the
run.
A filter pane on the right toggles what the page shows: message kinds
(Human, Claude, Notification, System, and thinking blocks) and each tool
that was actually called (Read, Edit, Bash, …), with a count next to each
and All/None buttons. Only kinds present in that session get a row,
and a message left with nothing visible is hidden along with its contents.
Assistant turns that report token usage show it in the card header next to
the timestamp, broken down the same way as in the picker.
If the file records a cost-state line, the header shows what the session
cost, with a collapsed breakdown of the time it spent and what each model
was asked to do. Sessions written without one show no cost at all.
Every event carries a { } button that shows the original jsonl line
behind it. The JSON is not embedded in the page — that would roughly double
a transcript already measured in megabytes — so the page holds only the
line's offset and length and asks the server for those bytes when you press
the button.
If the session spawned sub agents, each one's own transcript is a click
away: next to the tool call that started it, on the report it sent back,
and listed under Sub agents in the pane. Those pages render exactly like
a session and link back to the one they belong to. Command output, diffs, and file content
are collapsed by default (click to expand) so the page stays scannable.
The page isn't written to a file: ccr starts a small local HTTP server
(the first free port starting at 8000) that renders each session on
demand at http://localhost:<port>/<session_id>, then opens that URL via
$BROWSER. The server is started once per ccr run and reused for every
session you view afterwards; once it's running, the preview pane shows
Serving at: <url> for the highlighted session even if you haven't
pressed v on it yet. The picker keeps running — it doesn't exit after
opening a transcript.
Run ccr -n (or set CCR_NO_BROWSER=1) and v starts the server without
opening anything, printing serving at http://localhost:<port>/<session_id>
on the picker's bottom line for you to copy. ccr does this by itself when
there is no browser to open — $BROWSER unset with no platform fallback —
so a headless machine needs no flag at all.
The server only listens on 127.0.0.1, so to read the page from another
machine, forward the port over SSH:
ssh -L 8000:localhost:8000 user@serverThen open the URL that ccr printed, on your own machine.
Claude Code records a snapshot of every running (or not cleanly
terminated) session at ${CLAUDE_CONFIG_DIR}/sessions/<pid>.json,
including its pid and sessionId. Before building the session list,
ccr reads these files and checks that the recorded pid is both alive
and actually a claude process; stale or mismatched entries are ignored
(the files themselves are never deleted). For sessions with a live
process, the PID column in the list shows it.
MIT — see LICENSE.