Have you ever started a CLI project, forgot to note down the resume command, then burned a stack of tokens making the agent re-read the repo just to get its bearings? Same.
stash is an interactive launcher that resumes claude, codex, and
opencode sessions across every project you've ever opened. No more
copy-pasting your "here's where we left off" recap into a fresh chat. No more
hunting through ~/.claude/projects/<encoded-path>/<uuid>.jsonl for the right
session id. Just stash, pick a project, pick the conversation, and the right
tool launches in a new terminal window in the right directory with the right
--resume <id> flag already filled in.
$ stash
┏━┓╺┳╸┏━┓┏━┓╻ ╻
┗━┓ ┃ ┣━┫┗━┓┣━┫
┗━┛ ╹ ╹ ╹┗━┛╹ ╹
resume across claude · codex · opencode
┌ stash resume across tools
│
◆ Pick a project
│ ★ api 2m ago [claude·3]
│ web-app 31m ago [claude·2, codex·2]
│ deploy-scripts 9h ago [claude·3, opencode·2]
│ + Register a new project…
│ ✗ Delete a project (purge all sessions)…
│ 🧹 Sweep clutter (8 hidden / stale)…
│
│ ↑↓ navigate · enter select · space toggle · ctrl+c exit
└
- Fork-on-resume. A single
⤴ Fork on resume: ON/offtoggle in the session menu (space-toggleable like the other rows). Branches every resume into a fresh session id so the parent stays untouched. Wires through toclaude --fork-session,codex fork <id>, andopencode --fork --session <id>transparently. Also exposed asstash <name> --forkon the command line. - Rename-on-miss. When
stash <name>finds the registered directory no longer exists, it searches every indexed session for a live directory with the same basename and asks you to confirm the move (or pick between several matches). Never auto-decides.
stash search— fuzzy-match across every session you've ever opened in any of the three tools, with a live preview pane showing the last user prompt and assistant reply for the focused row. No more guessing which "refactor auth" session is the live one.- First-prompt indexing — search hits content, not just titles. If your conversation 11 days ago was about "fanvue nsfw" and the AI never generated a title, stash finds it anyway.
stash s/stash findaliases for the impatient.
$ stash search docker
┌ stash search across tools
◆ stash search › docker_
│
│ ─ results (3/3) ──────────────────
│ ▸ claude optima-docker Read the markdown file 2h ago
│ opencode optima-docker Deploying ARM Docker on AWS 29d ago
│ claude Lycan docker compose troubles 2d ago
│
│ ─ preview ────────────────────────
│ 469 msgs · ~344.6k tok · ~/Documents/Projects/Almas/optima-docker
│
│ you: Ok we can test it on a x86 24.04 lts tomorrow…
│
│ claude: Done. launch.md now has a Test plan section with a full runbook…
│
│ type to filter · ↑↓ navigate · enter resume · ctrl+c exit
stash doctor— three checks across claude / codex / opencode that validate each tool's on-disk format still parses. Run it after upgrading any of the three to catch schema drift before it surprises you.- Message counts and (where the tool exposes them) real token totals on every picker row, so two sessions with similar titles aren't a coin flip anymore.
- "Tested against" version table in the docs so you know which CLI
releases the parser was built for, and
doctorwill tell you when yours diverge.
- Lost resume commands. Every tool has a
--resume <id>(or equivalent), but the id is a UUID buried in a log somewhere. stash finds it for you. - Wasted tokens. Resuming the right conversation instead of starting fresh means the agent already knows your codebase, decisions, gotchas. No more "read the repo, then we'll begin."
- Three tools, one mental model. claude / codex / opencode each store sessions differently (JSONL files, rollout files + index, a SQLite db). stash normalises them into one picker.
- Multi-project sprawl. If you bounce between 30 projects like me, you forget what's in each one. stash groups every session by project directory and tags it with the last AI-generated title.
- Clutter from accidental invocations.
cd ~/.cache/huggingface/hub, ran claude once by mistake → it's now a "project" forever. stash hides obvious clutter and offers a one-tap Sweep to nuke it.
brew install SectorOPS/Stash/stash
brew install stash(unqualified) matches the unrelated Stash.app proxy tool's cask, not this CLI. Always use the fully-qualifiedSectorOPS/Stash/stashform. If you'd previously installed the Cask, you may also needbrew link --overwrite stashafter installing ours.
To upgrade:
brew update && brew upgrade SectorOPS/Stash/stashgit clone https://github.com/SectorOPS/Stash.git
cd Stash
./install.shRequires bun (brew install oven-sh/bun/bun). The installer
picks the best writable directory on your $PATH (/opt/homebrew/bin on a
Homebrew Mac, ~/.local/bin as a fallback) and drops a stash symlink there.
PREFIX=/usr/local ./install.sh to override.
Works on macOS and Linux. The new-terminal opener detects iTerm2, Terminal.app, Ghostty, WezTerm, Warp, kitty, Alacritty, gnome-terminal, konsole, xfce4-terminal, foot, and xterm. If you're already inside tmux it just opens a new tmux window.
stash # the main event — pick project, pick sessionA typical run:
- Project picker. Every directory where you've ever opened claude/codex/
opencode, sorted by recency. Registered projects get a ★. Clutter (caches,
~/Downloads,/tmp, missing dirs) is hidden by default. - Session menu. For the picked project: a row per existing session (showing the AI-generated title and the exact resume command that'll run), plus options to start a fresh session in any of the three tools.
- Toggle skip-permissions (
SPACEon that row) and new terminal window (SPACEon that row). Save defaults per registered project. - Launch. Spawns a new terminal window in the project's directory and runs the resume command for you.
stash <name> # resume registered project by name (most recent session, default tool)
stash <name> -y # …with skip-permissions
stash <name> --new # start a fresh session in that project's default tool
stash <name> --tool codex
stash <name> --session <uuid>
stash <name> --dry-run # print the command, don't run itstash add [dir] # register cwd (or dir) as a project
stash add --name api --tool claude --dir ~/code/api
stash ls # list visible projects
stash ls --all # include hidden clutter
stash rm <name> # unregister (keeps sessions on disk)
stash where # print registry path
stash edit # open registry in $EDITORstash doctor # verify each tool's session format still parsesRuns a per-tool check that opens one session, looks for the fields stash
depends on, and reports ok / warn / error with a one-line explanation.
Exits non-zero on any error so you can wire it into CI or a shell precmd.
Run it after upgrading claude / codex / opencode to catch schema drift early.
-y, --yolo add --dangerously-skip-permissions (claude/codex)
--here run in current terminal instead of new window
-w, --new-window force a new window even if default is --here
--new start fresh session instead of resuming
--tool TOOL override default tool (claude|codex|opencode)
--session ID resume a specific session uuid
-n, --dry-run print the command, don't launch
-a, --all (with `ls`) include hidden clutter
| Tool | Source |
|---|---|
| claude | ~/.claude/projects/<encoded-path>/<uuid>.jsonl |
| codex | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl + session_index.jsonl |
| opencode | ~/.local/share/opencode/opencode.db (SQLite) |
Titles come from each tool's own metadata: claude's ai-title (falling back to
the last user prompt), codex's thread_name in the session index, opencode's
session title column.
Token totals are pulled from each tool's own usage tracking and shown where
they're accurate: claude records usage.input_tokens / usage.output_tokens
per assistant turn and opencode records tokens.input / tokens.output
per message — both are summed into the ~Nk tok you see in the picker.
codex doesn't record per-turn usage in its rollouts, so codex rows show
only N msgs. The asymmetry is deliberate — better to show a real number
where one exists than to fake it everywhere.
Stash is tested against the following CLI versions. stash doctor will tell
you if your install diverges in a way that breaks parsing.
| Tool | Tested up to | Storage format |
|---|---|---|
| claude | 2.1.x | JSONL with ai-title, last-prompt, user, assistant events |
| codex | 0.129.x | rollout JSONL with session_meta + response_item envelopes |
| opencode | 1.14.x | SQLite schema with session / message / part tables |
The encoded-path scheme claude uses (/ and . both map to -) is ambiguous
on decode, so stash walks the real filesystem to disambiguate (e.g. it knows
-home-user--cache-huggingface-hub is ~/.cache/huggingface/hub, not
~/cache/huggingface/hub).
~/.config/stash/registry.json (or $XDG_CONFIG_HOME/stash/registry.json):
{
"version": 1,
"projects": [
{
"name": "api",
"dir": "/home/you/code/api",
"defaultTool": "claude",
"skipPermissions": false,
"newWindow": true,
"lastSessionId": "00000000-0000-0000-0000-000000000000",
"lastTool": "claude",
"addedAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]
}stash edit opens it in $EDITOR if you need to clean it up by hand.
-y / --yolo translates per-tool:
- claude →
--dangerously-skip-permissions - codex →
--dangerously-bypass-approvals-and-sandbox - opencode → no CLI flag exposed; the option is silently ignored
stash gives you three flavours of cleanup, from gentle to scorched-earth:
✂ Delete sessions…(inside a project's session menu): multi-select checklist · prune duplicates · keep the most-recent N.✗ Delete a project (purge all sessions)…(in the project picker, or inside a session menu): nuke every session attached to a project across all three tools, plus its registry entry. Asks you to type the project name to confirm.🧹 Sweep clutter…(in the project picker, when there's anything to sweep): auto-finds sessions in obvious junk paths (~/.cache,~/Downloads,~/Library,/tmp,/var, etc.) and dirs that no longer exist, previews exactly what'll be removed, then deletes the lot in one confirm.
The on-disk project directory is never touched — only the session files / SQLite rows.
CLAUDE_CODE_SESSION_ID— when set (it is whenever stash is launched from inside an active Claude Code conversation), that session is excluded from the picker, since deleting it just gets recreated by the next assistant message.NO_BANNER=1— suppresses the ASCII logo at launch.XDG_CONFIG_HOME— overrides the default~/.configlocation for the registry.
MIT.
