Fast TUI to find and resume Claude Code and Codex CLI sessions across every config directory
you use — multiple ccs accounts, ~/.claude,
$CLAUDE_CONFIG_DIR, ~/.codex, or any directory you configure. It doesn't manage sessions;
it finds one and hands off to the right launcher.
- One list for every account. ccs accounts,
~/.claude,$CLAUDE_CONFIG_DIR,~/.codexand any directory you configure. Accounts that share a transcript store are listed once, and can be resumed through any of them (Ctrl-Apicks). - Search as you type. Titles, project paths, branches and first prompts filter instantly; matches inside the conversations themselves arrive a moment later, below a divider.
- Shows what's running. Live sessions for both agents are marked, with their pid, so you never resume one twice. On Windows itself (not WSL), only Claude sessions get this treatment — see Codex below.
- Enter does the right thing. A stopped session resumes through its own launcher; a running one brings its terminal to the front instead, switching to the right tab.
- Reads across Windows and WSL. Each side lists the other's sessions, and running state is
detected both ways. Where ccpick can't launch across the boundary it hands you the command to
paste (
ccopies it). - Conversation preview. Scroll the transcript beside the list before committing to it.
- Fast. A warm start is under 100 ms on a few hundred transcripts; metadata is cached.
Linux (x86_64, arm64) and macOS (Intel, Apple Silicon):
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/outsharked/ccpick/releases/latest/download/ccpick-installer.sh | shWindows (PowerShell):
powershell -ExecutionPolicy Bypass -c "irm https://github.com/outsharked/ccpick/releases/latest/download/ccpick-installer.ps1 | iex"On macOS, sessions are listed and resumable but never shown as running.
ccpick # browse everything
ccpick docker cache # start with a query
ccpick list migration # non-interactive TSV
ccpick sources # show discovered sources and stores
ccpick export # recent sessions as JSON (see below)
ccpick --config-dir ~/.claude-workccpick export prints your recent sessions as JSON, so you can hand them to an LLM and ask
what you've been working on. Whole transcripts would be far too large, so each session is
a short summary: its title, project, branch, timestamps and message count, plus the opening
prompt (the goal) and the tail of the conversation (where it ended up), each capped.
ccpick export > recent.json # last 14 days
ccpick export --since 2026-09-01 --tail-chars 1500
ccpick export --since 36h docker # only sessions matching "docker"
ccpick export --head-chars 0 --tail-chars 0 # metadata onlyThe output is one JSON array with a session per line, most recently active first:
{"agent":"claude","id":"…","title":"Fix docker build cache","project":"~/code/app","branch":"main","started":"2026-09-28T09:12:03Z","last_active":"2026-09-28T10:40:51Z","messages":42,"source":"c1","resume_command":"cd ~/code/app && ccs c1 --resume …","opening_prompt":"…","tail":"assistant: …","truncated":true}To read one session in full, pass its id (or a unique prefix) to show:
ccpick show 877a7dd3 # readable transcript, conversation text only
ccpick show 877a7dd3 --format json # one JSON object with a `conversation` array
ccpick show 877a7dd3 --max-chars 500 # cap each message--since takes an age (36h, 14d, 2w) or a date (2026-09-01) and defaults to 14d;
--head-chars and --tail-chars default to 300 and 700, and 0 omits that field.
truncated is true when either was cut. Warnings go to stderr, so stdout is always JSON.
Type to filter titles, project paths, branches and first prompts instantly; matches inside conversation text appear below a divider shortly after.
| Key | Action |
|---|---|
Enter |
Resume, or focus the terminal of an already-running session (never resumes it twice) |
Ctrl-A |
Cycle which source/account resumes the session |
Ctrl-R |
Refresh: rescan for sessions started or stopped since you opened it |
Ctrl-L |
Live sessions only |
Ctrl-S |
Sort by last activity / created |
→ / ← |
Focus the conversation preview / the session list (Tab toggles) |
↑ / ↓ |
Move selection (list) or scroll one line (preview) |
PgUp / PgDn |
Move 10 sessions (list) or scroll a page (preview) |
Home / End |
First/last session (list) or top/bottom of the conversation (preview) |
Esc |
Clear the search; with nothing to clear, quit after a confirmation (Ctrl-C quits outright) |
ccpick web # serve the session list and open it in a browser
ccpick web --no-open # print the URL instead
ccpick web --port 4200 # pin the port (default: chosen by the OS)
ccpick web --refresh 30 # seconds between refreshes (default: 10)ccpick web is a companion to the TUI, not a replacement: it serves the same session
list, built from the same catalog, as a page at http://127.0.0.1:<port>, and opens it
in your browser. It refreshes automatically and does nothing on its own besides that —
no session starts until you click one.
Click a session that's running and its terminal comes to the front, the same as
Enter in the TUI. Click a stopped one and it resumes in a new terminal. Where ccpick
has no terminal to open, or the session lives across the Windows/WSL boundary, the page
shows the command to paste instead, with a copy button — the same fallback the TUI
already offers.
It's loopback-only: the server binds 127.0.0.1, refuses any request whose Host or
Origin isn't itself, and mints a fresh token per run that's baked into the URL it
opens and required on every request after that. There's no flag to bind it anywhere
else, no accounts, and nothing persisted beyond ccpick's own metadata cache.
cargo build --no-default-features builds the TUI with no HTTP dependency at all, and
that build has no web subcommand.
Since web is a subcommand, searching for the literal word "web" needs ccpick -- web
(or ccpick list -- web for TSV) — plain ccpick web starts the portal instead. The same
goes for the other command names: list, export and sources.
Filter the list with the chips under the search box: live sessions only, and — when a
machine has sessions in more than one environment — by Windows, WSL or Linux. Ctrl-L toggles
the live-only filter, as it does in the TUI. Filtering happens in the page, so it is instant and
combines with whatever you have typed.
Discovered in order (earlier wins as the default launcher):
- ccs accounts from
~/.ccs/config.yaml(default account first) →ccs <account> --resume <id> $CLAUDE_CONFIG_DIR→CLAUDE_CONFIG_DIR=<dir> claude --resume <id>~/.claude→claude --resume <id>(withCLAUDE_CONFIG_DIRunset)[[source]]entries in~/.config/ccpick/config.toml--config-dirflags
Sources whose projects/ resolve to the same directory share one transcript store, so each
session is listed once and can be resumed through any of them.
Codex CLI threads from ~/.codex (or $CODEX_HOME) are listed alongside Claude's, read from
Codex's own session index (state_<n>.sqlite) rather than the rollout files directly. Only
threads started from the CLI or the editor are shown — Codex spawns subagent threads as children
of a run that nobody resumes on their own, so those are left out, along with threads that never
got a title or a first message.
A thread's title, cwd and other metadata are cached against its rollout file's size and mtime,
same as Claude's. Renaming a thread in Codex without adding a new message to it won't update
the title here until the next message arrives, or you run with --no-cache.
Running Codex sessions are detected by walking this host's own /proc: a live codex process
holds a file open that identifies its thread, so a match there means it's running. Either of two
files counts — a lock under thread-writer-locks/, or the thread's own rollout file — because
which ones are open varies with the Codex version and with how far the session has got. A
freshly started session that hasn't exchanged a message yet may show neither, and won't be
listed at all until Codex writes it to the index. That works on Linux and inside
WSL. It does not work on a Windows host, which has no /proc and no handle enumeration to stand
in for it — on Windows, Codex sessions always resume rather than switching to a live terminal,
even one that's actually running. Unlike Claude's pid-file records, this doesn't reach across
the Windows/WSL boundary either: a WSL host only sees Codex sessions running in that same distro,
not ones on the Windows side.
On a Windows machine with WSL, ccpick also lists the other side's sessions:
- In WSL, Windows users' Claude Code and Codex data under
/mnt/c/Users/<user>appears aswin:<name>sources. - On Windows, sessions in running WSL distros appear as
wsl:<name>(or<distro>:<name>with several distros). Stopped distros aren't started.
Those sessions are searchable like any other, with one exception: a Codex thread's index records the path of its rollout file in its own environment's form, so from the other side ccpick can list a foreign Codex session and resume it, but can't open the transcript — the preview is blank and full-text search doesn't reach inside it. Titles, paths and timestamps all come from the index and are unaffected.
For Claude, running ones are shown as running whichever side ccpick is on — that's what the
console-title and interop-socket matching below does. Codex's running detection (see
Codex above) doesn't reach across this boundary: a Codex session only shows as running
when ccpick is on the same side it's running on. Pressing
Enter on a session that isn't running opens a dialog with the command to paste into a shell on
the other side (c copies it), since ccpick doesn't launch across the boundary.
Pressing Enter on a session that is running brings its terminal to the front instead of resuming it, switching to the right tab where the terminal supports tabs (Windows Terminal does). It takes a second or two, since the work runs through PowerShell.
- Windows sessions are found through their console: ccpick briefly sets the console title to a unique marker, selects the tab showing it, then restores the previous title.
- WSL sessions are found through the session's own interop socket, and the tab is matched by title — so if the terminal's title has drifted from the one ccpick shows, the window is raised without switching tabs. This works from Windows and from another distro too, by running the helper inside the session's own distro.
- If no terminal can be found, the status line reports that the session is running, with the reason.
[environments]
auto = true # set false to only scan the native environment
wsl_distros = ["Ubuntu"] # Windows only: also scan these distros when stopped (boots them)A [[source]] may point at the other side's path; its environment is inferred, or set env = "windows" / env = "wsl:<distro>".
~/.config/ccpick/config.toml (optional):
[claude]
ccs = true # auto-detect ccs accounts
home = true # include ~/.claude and $CLAUDE_CONFIG_DIR
[codex]
home = true # include ~/.codex and $CODEX_HOME
[[source]]
name = "work"
config_dir = "~/.claude-work"
# command = ["my-wrapper", "--profile", "work"] # default: claude with CLAUDE_CONFIG_DIR
[[source]]
agent = "codex"
name = "codex-work"
config_dir = "~/.codex-work"
# command = ["my-wrapper", "--profile", "work"] # default: codex with CODEX_HOMEMetadata is cached in ~/.cache/ccpick/meta.json (--no-cache to bypass).
With a Rust toolchain (1.85 or newer, for edition 2024):
cargo install --git https://github.com/outsharked/ccpickOr from a clone, which is also how you get a binary to run in place:
git clone https://github.com/outsharked/ccpick
cd ccpick
cargo build --release # ./target/release/ccpick
cargo install --path . --root ~/.local # or install it to ~/.local/binTasks are managed with mise (mise tasks lists them):
mise dev # run the TUI from source
mise dev -- list docker # args after -- pass through to ccpick
mise check # fmt check, clippy (warnings as errors), tests
mise test # tests only
mise test-windows # WSL only: run the test suite as Windows binaries via interop
mise lint-windows # WSL only: clippy for the Windows target
mise format # format
mise install-bin # install the release binary to ~/.local/binRun the TUI from a plain shell rather than inside a Claude Code session, since resuming a session replaces the ccpick process.
mise test-windows and mise lint-windows only work in WSL, with the mingw toolchain and
rustup target add x86_64-pc-windows-gnu installed.
mise release 0.2.0 # bump version, run checks, commit, tag v0.2.0
git push --follow-tags # CI builds binaries and publishes the GitHub release + installerSee docs/specs/2026-09-16-ccpick-design.md. Agent-specific code lives behind a
Provider trait in src/providers/ — Claude Code and Codex CLI are both built on it today, and
other agents can be added the same way.
MIT — see LICENSE.
