Dig through your Claude Code session history from the
terminal, following ls/rm conventions. quarry inspects Claude's on-disk state under
$CLAUDE_CONFIG_DIR (default ~/.claude) and ~/.claude.json — listing, inspecting, and
removing sessions only ever read files, never launching Claude or any agent. (Resuming is
the one exception: quarry fzf and the detail view's Resume line hand off to
claude --resume.) One command, a few subcommands:
quarry ls— list sessions, or show one in detail.quarry rm— remove a session's on-disk artifacts (with confirmation).quarry rename— set session titles from a template, in bulk or one at a time.quarry fzf— interactively find a session (fuzzy over names/prompts/replies) and resume it. Requiresfzf.quarry completions— emit a shell completion script (fish/zsh/bash).
Independent project, not affiliated with or endorsed by Anthropic. Claude and Claude Code are trademarks of Anthropic, PBC. "for Claude Code" is descriptive/nominative use.
Dependency-free: Python 3.8+ and, for quarry fzf, fzf
on your PATH. The tool runs straight from the checkout; make install just symlinks the
entry point onto your PATH (it resolves the symlink to find src/, so the repo can stay
put).
git clone https://github.com/dmitrii/quarry.git && cd quarry
make install # symlink bin/quarry -> ~/.local/bin/quarry
make install-completions # fish autoloads; zsh/bash print the one rc line to add
make install-config # optional: starter config.ini (won't clobber an existing one)Override locations with PREFIX= / BINDIR= etc. (make help lists them). Make sure the
bin dir is on your PATH — fish: fish_add_path ~/.local/bin. Undo with make uninstall.
quarry ls [-l] [-a] [-c] [-r] [-t] [-S] [--size {log}] [--color {auto,always,never}] [query]
quarry ls— one session per line: the session's name, or its UUID when it has no custom name. Sorted by last interaction, most recent first (likels -t).-l— long view. Columns: open marker, size, timestamp, launch directory (fixed 20 chars), and name/UUID. A trailing↳Nmarks a session that spawnedNsubagents (see below).-a— also list removed sessions still referenced in~/.claude.json(in grey), likels -asurfacing otherwise-hidden entries.-c— sort by start time instead of last interaction.-S— sort by size, largest first.-r— reverse the sort (oldest/smallest first).-t— accepted forlsfamiliarity (time sort is already the default).--size {log}— which notion of size to show and sort by (see below).--color {auto,always,never}—autocolorizes only on a TTY and respectsNO_COLOR.
In -l, a leading * (bold green) marks a session that is currently open in a
live claude process, determined from $CLAUDE_CONFIG_DIR/sessions/<pid>.json (the PID
must still be alive; stale entries are ignored).
Subagents (sidechains). When Claude spawns a subagent, its transcript is written to a
separate agent-*.jsonl file whose records are all isSidechain and point back at the
session that spawned them. These aren't resumable conversations of their own, so they're
kept out of the listing; instead each parent shows a ↳N count in -l and a
Sidechains row in the detail view, and quarry rm removes them along with the parent.
$ quarry ls -lS
* 1.2M Jan 15 09:42 my-app 2026-01-15-refactor-auth-module
486K Jan 14 16:20 my-app 2026-01-14-add-dark-mode-toggle
72K Jan 12 11:03 scratch 2026-01-12-debug-flaky-test
18K Jan 10 14:55 scratch 8f3c1a92-4b7e-4c1d-9a2f-1e6d0b5c7a34
Pass a UUID (or unique prefix) or a name to inspect one session. Matching precedence: exact UUID → exact title (custom or AI) → UUID prefix → case-insensitive title substring. Ambiguous queries list the candidates.
$ quarry ls debug-flaky
Track down and fix the intermittent test failure
Session ID 8f3c1a92-4b7e-4c1d-9a2f-1e6d0b5c7a34
Name 2026-01-12-debug-flaky-test
AI title Track down and fix the intermittent test failure
Directory /Users/you/code/scratch
Started 2026-01-12T11:03:54-08:00
Last update 2026-01-12T11:41:12-08:00
Duration 37m 18s
Process NOT OPEN
Resume cd /Users/you/code/scratch && claude --resume 8f3c1a92-…
User prompts 9
Agent replies 14
Tool calls 22
Tokens ↓ 8.1k generated · ↑ 96.4k peak context
Log size 71.6 KiB (73,301 B)
Latest context 42.0k tokens (41,984)
Model claude-opus-4-8
Git branch main
CLI version 2.1.197
First prompt …
Last prompt …
Notes:
- Name vs AI title.
Nameis the user-set title (/rename);AI titleis Claude's auto-generated one. Both are always shown so it's clear which is which. - Resume.
claude --resumeis scoped to the current directory's project, so the command prependscd <launch dir> &&unless you're already there. (Running it from the wrong directory is whyclaude --resume <id>reports "No conversation found".) - Agent replies counts distinct model responses (unique message IDs), not raw log records. Tokens are summed generated output and peak input context from usage data.
- Latest context is how full the window was on the last request (input + both cache
buckets of the final main-thread
assistantrecord) — distinct from the peak on the Tokens line. Useful for gauging how heavy a session is to resume. - Prompt previews grow to fill a wider terminal.
- Removed sessions. The detail view works for a removed session too (resolvable by
UUID/name even without
-a): log-only fields show-, while directory, duration, tokens, cost, and model are recovered from~/.claude.json's last-run record.
quarry rm [-f] [-n] [--color {auto,always,never}] [query]
Removes a session's on-disk artifacts, resolving query exactly as the detail view does
(ambiguous queries are refused, not guessed). It deletes the files/dirs named after
the UUID, plus the session's subagent transcripts:
projects/<encoded-cwd>/<uuid>.jsonland its<uuid>/sidecar dirsession-env/<uuid>/file-history/<uuid>/projects/<encoded-cwd>/agent-*.jsonl— the session's sidechain (subagent) transcripts
Centralized files (history.jsonl, ~/.claude.json) are left untouched — the
orphaned references there are harmless, and this keeps quarry rm from ever rewriting
shared state. (A session you remove therefore lingers as a grey -a entry until Claude
next overwrites that directory's lastSessionId.)
No argument — offers the session Claude last ran in the current directory
(~/.claude.json's lastSessionId for $PWD), so right after quitting a session you can
just type quarry rm. It considers only that one session — never falling back to older
ones — and declines cleanly if it's already removed or still running. -f is refused with
no argument (an inferred target must be confirmed interactively).
Safety:
- Refuses to remove a session that is currently open in a live
claudeprocess. - Prompts before deleting (
rm -istyle).-fskips the prompt;-npreviews the exact file list and total size and deletes nothing. Without a TTY and without-f, it refuses rather than delete unprompted.
$ quarry rm -n debug-flaky
Would remove session 8f3c1a92-4b7e-4c1d-9a2f-1e6d0b5c7a34 "Track down and fix the intermittent…"
72K ~/.claude/projects/-Users-you-code-scratch/8f3c1a92-….jsonl
0 ~/.claude/session-env/8f3c1a92-…/
2 item(s), 71.6 KiB (73,301 B)
quarry rename [-t TEMPLATE] [--retitle] [-n] [--model M] [--color {auto,always,never}] [selector]
Sets session titles — the name /rename sets — from a template, so a year of
UUID-named sessions can be titled without resuming each one. It writes the title exactly
as Claude Code does: a custom-title record appended to the session's transcript (nothing
else is touched). Each session is shown in detail with its proposed title, then a prompt: Enter accepts
it, type a new title to replace it, - skips this session, Ctrl-C aborts the
batch.
- selector — a UUID/prefix or shell glob matched against each session's UUID or
current name. Omit it to target the last session run in the current directory (like
quarry rmwith no argument) — leave a session and just typequarry rename. - Already-titled sessions are skipped unless
--retitle(or the selector names exactly one). Subagent sidechains are never touched. Open sessions are refused. -n/--dry-runlists the matched sessions and the rendered template (with$SUMMARYleft literal — no model calls), and writes nothing.
The title comes from a template of $VAR placeholders; ${VAR:-fallback} uses fallback
when VAR is empty. The default (default_template in config) is ${AI_TITLE:-$SUMMARY}
— reuse the auto-generated title if there is one, otherwise generate a fresh summary.
- Offline / free:
$START_DATE,$LAST_ACTIVITY_DATE(YYYY-MM-DD),$LAUNCH_DIR(launch-dir basename),$GIT_PROJECT(enclosing.gitroot basename),$BRANCH,$UUID,$UUID8,$AI_TITLE(kebab of Claude's auto title). - Generated:
$SUMMARY— a 3–8 word kebab summary. This is the only variable that calls a model, and only when actually reached (so${AI_TITLE:-$SUMMARY}costs nothing when an AI title already exists).
$SUMMARY is produced by the summary_command in [rename] config. The contract:
quarry pipes the session's user-prompt context to the command's stdin; the command prints
the title to stdout. Swap in any command (another model, a local LLM) — nothing about the
wording is baked into quarry. The shipped default shells out to the claude CLI:
claude -p --no-session-persistence --model haiku --effort low --tools "" --system-prompt "…"
which reuses your existing Claude Code auth (no API key), leaves no session on disk (so
titled sessions don't pollute quarry ls), and needs no extra dependency. Tune the model,
effort, wording, summary_context_chars, and summary_timeout_secs in the config
(make install-config drops a starter copy). It does not feed the agent's commands
(tool_use) or their output (tool_result) — those would need a persistent index.
$ quarry rename -n '*'
d0809692-… -> 2026-03-14-memfis-migrate-config-store
9fcdb292-… -> 2025-08-12-reckoner-multi-cloud-data-inventory
Interactive picker. It's a thin launcher — quarry ls --tsv piped into fzf — so fzf
provides the whole UI and the keybindings decide what happens to the session you pick:
| key | action |
|---|---|
enter |
resume it (cd <dir> && claude --resume <uuid>) |
ctrl-x |
delete it (quarry rm), then refresh the list |
ctrl-y |
copy its UUID to the clipboard |
tab |
cycle the search scope: names → prompts → replies (looping) |
The header shows the active scope in CAPS so the current search breadth is always
visible. enter refuses to resume a session that's currently open in a live process
(checked at resume time) — it prints a message and bails rather than risk a second attach.
Deleting an open session via ctrl-x is likewise refused by quarry rm.
The preview pane is quarry ls <uuid> (the detail view), live as you scroll. Matching is
exact-substring (fzf --exact) — fuzzy subsequence matching is useless once the
searchable text includes whole transcripts (any short query's letters appear somewhere
in a 100k-token blob).
Scope (cycled with tab) controls how much text is searchable, via
quarry ls --tsv --scope:
names— session title + AI title (cheap; the default)prompts— the above + everything you typedreplies— the above + the agent's text responses, its chain-of-thought, and the text of any subagents it spawned (the last two are configurable — see below)
prompts/replies parse each log, so they're heavier than names — but only the picker
uses them, on demand.
What replies searches is configurable via ~/.config/quarry/config.ini (override the
directory with $XDG_CONFIG_HOME; make install-config drops a starter copy). A missing or
malformed file falls back to the defaults:
[search]
# Extra content the `replies` scope searches (names/prompts are unaffected).
sidechains = true # also search the session's subagent transcripts
thinking = true # include the agent's chain-of-thought, not just visible repliesreplies always includes the agent's visible text responses; the two toggles add its
reasoning and its subagents' text on top. It does not search the exact commands the
agent ran (tool_use) or their output (tool_result) — those are far larger and would
need a persistent index.
Selecting text from the fzf screen (e.g. to copy a path out of the preview): fzf captures the mouse, so use your terminal's bypass modifier — in iTerm2, hold ⌥ Option and drag, then ⌘C.
quarry ls --tsv emits UUID⇥label⇥dir⇥date⇥searchable and is the machine-readable
counterpart to the normal listing; you can pipe it into your own tools too.
Resume is directory-scoped, so on enter the wrapper cds into the session's launch
directory before exec claude --resume. The resume happens in the wrapper after fzf
exits (fzf prints the selection; the script execs Claude) rather than via fzf's become —
with become the resumed session renders but never receives keyboard input, because it
doesn't inherit the terminal on stdin. Removed sessions aren't listed (they can't be
resumed).
quarry completions {fish,zsh,bash} prints a completion script to stdout;
make install-completions installs all three. Reliability differs by shell:
- fish — installed to
~/.config/fish/completions/quarry.fish, which fish autoloads. Includes dynamic completion of session names/UUIDs (viaquarry ls --tsv). - zsh / bash — static completion of subcommands and flags. The Makefile installs the
file and prints the one rc line to add (
fpath+=…beforecompinit, or asource …).
Size is pluggable via the SIZE_METRICS registry in src/claude_sessions.py; --size
and -S both read from it. Today the only enabled metric is log (log-file bytes on
disk), which is cheap — a stat() per file.
A metric declares a unit (bytes/tokens) and whether it's expensive (needs the log
parsed). Cheap metrics read a field off Session; expensive ones are filled by
ensure_sizes() before listing. A context metric (latest post-prompt context size) is
wired up and commented out in the registry — uncommenting it enables --size context for
the column and -S sorting. It's left off by default because, unlike log, it must parse
every log just to list.
bin/quarry— the entry point: a dispatcher that routes each subcommand to its own argument parser. Putssrc/onsys.path.bin/quarry-fzf— thin shell launcher aroundfzf+quarry ls/quarry rm.src/claude_sessions.py— shared core: discovery, matching, log analysis, artifact listing, config, title templating/generation, and formatting.src/cmd_ls.py,src/cmd_rm.py,src/cmd_rename.py— thels,rm, andrenamecommand implementations.
Everything comes from $CLAUDE_CONFIG_DIR (default ~/.claude): projects/*/*.jsonl for
the logs, sessions/<pid>.json for liveness, and ~/.claude.json for per-directory
lastSessionId pointers (used to surface removed sessions). These are undocumented Claude
Code internals (developed against CLI v2.1.x) and may change; the tool degrades gracefully
when files are missing or unparseable.
BSD 2-Clause. See LICENSE.