Session management for Claude Code.
Claude Code tracks sessions but gives you no way to see them. No list, no switching, no way to tell what's running in the background. claude-spaces adds all of that — a persistent tmux side panel that shows every session across every project, with instant switching and background activity alerts.
+------------------------------------+------------------------------+
| | ~wired/ |
| Active claude session | write a wrapper... 5m |
| or welcome screen | sessions 14h |
| | configure verbose... 4h |
| | |
| | ─ other projects ─ |
| | on-point-camera |
| | test-framework 2h |
| | camera firmware 3h |
| | |
| | old-webapp |
| | fix route bug 1d |
| | |
| | ─ inactive ─ |
| | old-project |
| | |
| | : menu / search |
| | Q:detach ?:help |
| | claude-spaces v0.9.10-dev |
+------------------------------------+------------------------------+
left slot picker (30 cols default)
claude-spaces avoids altering Claude Code's internal files. We only read from
~/.claude and write all state to claude-spaces-specific directories.
That said, this is a hobby project and Claude was involved with most of the code, so I encourage caution. Test it out before using it with valuable projects.
brew tap wired/tap && brew install claude-spaces # macOS / Homebrew
yay -S claude-spaces # Arch (AUR)
make install # /usr/local/bin (may need sudo)
PREFIX=~/.local make install # ~/.local/bin
You can also just clone and run directly if all dependencies are met. It will warn you.
Requires bash 4.3+, tmux 3.0+, jq.
claude-spaces # launch
claude-spaces --restart # restart the server (e.g. to apply an update)
claude-spaces --reset # kill all managed panes, clear state
claude-spaces --help
Run it from any project directory. It creates a dedicated tmux server, opens a session picker on the right, and launches Claude Code on the left.
claude-spaces inherits your tmux prefix from tmux.conf and works with any
prefix. That said, the keybindings are designed around ` (backtick):
prefix + `— smart terminal toggle (open, then toggle focus)prefix + a— sends a literal`(so you don't lose the character)
If you'd like to try it, add to your ~/.tmux.conf:
set-option -g prefix `
bind-key a send-prefix
Or, to only use ` as prefix inside claude-spaces, add to
~/.config/claude-spaces/config:
prefix=`All prefix bindings are configurable via the same config file (see Configuration).
Session picker — all sessions for the current project in a persistent side
panel. Navigate with j/k, jump with 1-0, search with /. You can also
use the tmux prefix for even faster navigation
Cross-project discovery — sessions from other running projects appear automatically. Hit Enter to switch. Inactive projects (not currently running) are discovered and can be resumed.
This allows you to use claude-spaces with worktrees as well, you just have to create them yourself.
Project names — when multiple projects share the same directory name, the
picker automatically disambiguates them with minimal path suffixes. You can
also set custom names via prefix + p or the : command menu — useful for
worktrees or any project where the directory name isn't descriptive enough.
Bell detection — when Claude finishes in a background session, the picker highlights it in red. Never miss a completed task again.
Per-session terminal — each session gets its own shell pane, either below
it (prefix + `) or to the side (prefix + Tab). Both open the terminal
and toggle focus between it and the Claude pane. prefix + t toggles terminal
visibility on/off. Resize freely — size is saved per-session.
Search and filter — type / to filter sessions by name across all
sections. Matches update as you type.
Isolated per-project servers — each project gets its own tmux server. No cross-contamination of keybindings, window state, or bell hooks.
Keybinds
claude-spaces takes full ownership of the tmux prefix key table on its dedicated server. Stock tmux bindings are disabled. Your tmux.conf is sourced for visuals (colors, mouse, status) but all keybindings are managed by claude-spaces.
| Key | Action |
|---|---|
j / k / ↑ / ↓ |
Move cursor (skips headers) |
1-9, 0 |
Jump to Nth session (0 = 10th) + focus |
Enter |
Load + focus session (remote/inactive: switch project) |
Space |
Load session (stay in picker) |
H / L |
Load + focus session |
a / i |
Focus Claude pane |
h / l / ← / → |
Move between panes |
/ |
Search/filter sessions |
: |
Command menu (new, fork, rename, close, hide, shutdown) |
? |
Show help screen |
Q |
Detach (exit) |
R |
Reload picker in-place (picks up code changes) — see note below |
Note on
Rafter an update. Most updates hot-reload in place. If an update changes the session layout, the new picker can't run against the running server —Rstops it instead, and tells you to runclaude-spacesagain. Your sessions restart, but conversations are not lost: they come straight back in the picker, resumable.claude-spaces --restartdoes the same thing without going through the picker. Runningclaude-spaceswhile such an update is pending prints a notice and leaves the server alone, so nothing is ever restarted behind your back.
| Key | Action |
|---|---|
prefix + Enter / prefix + i |
Focus Claude pane |
prefix + Space |
Toggle picker / Claude pane |
prefix + ` |
Smart terminal — bottom (open/focus toggle) |
prefix + Tab |
Smart terminal — side (open/focus toggle) |
prefix + t |
Toggle terminal on/off (unconditional) |
prefix + a |
Literal grave (`) |
prefix + h / prefix + ← / prefix + S-← |
Select pane left |
prefix + j / prefix + ↓ |
Select pane down |
prefix + k / prefix + ↑ |
Select pane up |
prefix + l / prefix + → / prefix + S-→ |
Select pane right |
prefix + J / prefix + S-↓ |
Next session + focus |
prefix + K / prefix + S-↑ |
Prev session + focus |
prefix + 1-9, 0 |
Jump to Nth session + focus |
prefix + / |
Focus picker + search |
prefix + c |
New session |
prefix + f |
Fork session (with confirm) |
prefix + x |
Close session (with confirm) |
prefix + r |
Rename session |
prefix + p |
Rename project |
prefix + H |
Hide session (with confirm) |
prefix + s |
Shutdown (kill server) |
prefix + R |
Reload picker |
prefix + z |
Zoom/maximize pane |
prefix + [ |
Copy mode |
prefix + ] |
Paste buffer |
prefix + PgUp |
Copy mode + scroll up |
prefix + d |
Detach |
prefix + : |
Command menu |
prefix + ? |
Show help screen |
prefix + + |
Refresh/rescan |
prefix + F12 |
tmux command prompt (escape hatch) |
prefix + M-↑/↓/←/→ |
Resize pane (2 cells) |
prefix + C-↑/↓/←/→ |
Resize pane (5 cells) |
Bell Detection
Background sessions that finish can ring the terminal bell, highlighting them
in red in the picker. Add a Claude Code Stop hook to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "printf '\\a' | jq -Rsc '{terminalSequence: .}'"
}
]
}
]
}
}Claude Code runs hooks without a controlling terminal (v2.1.139+), so the older
printf '\a' > /dev/tty recipe now fails with No such device or address.
Returning the bell in the terminalSequence field instead makes Claude Code
emit it through its own terminal, where tmux sees it. Requires Claude Code
v2.1.141+. Bell state clears when the session is brought to the foreground.
Rendering (recommended: tui: fullscreen)
Recommended when using Claude Code under claude-spaces. In ~/.claude/settings.json:
{
"tui": "fullscreen"
}By default Claude Code draws into the terminal's normal buffer, so its output lands in tmux's scrollback. tmux reflows scrollback whenever a pane's width changes — so resizing your terminal (or opening a side terminal) can mangle the history, with parts of the transcript doubled or tripled.
tui: fullscreen moves Claude onto the alternate screen, which tmux never
writes into scrollback and never reflows, so the mangling cannot occur.
Switching sessions does not resize the Claude pane — the picker moves, not Claude — so swapping between sessions is already reflow-free either way.
Trade-off: you lose tmux-native scrollback and copy-mode for the Claude pane — use Claude's own scrolling instead. The picker and terminal panes are unaffected.
Configuration
Config file: ~/.config/claude-spaces/config (created on first run with
commented defaults; respects $XDG_CONFIG_HOME).
# Picker pane width: characters (e.g. 30) or percentage (e.g. 20%)
# picker_width=30
# Picker pane position: "right" (default) or "left"
# picker_side=right
# Sessions modified within this many minutes sort at the top (running sessions
# first, then by name); older sessions sort by most recent first below them.
# recent_threshold=10
# Max length for tmux window names
# window_name_len=12
# Path to tmux.conf to source on dedicated server (empty = don't source)
# tmux_conf=~/.tmux.conf
# Directory for runtime state. Defaults to $XDG_RUNTIME_DIR/claude-spaces, or
# /tmp/claude-spaces-$UID when that is unset. Set this if your /tmp is reaped
# while sessions are still live. Must be an absolute path (~/ is expanded).
# A persistent location means state survives reboot and keeps a small entry per
# project; `claude-spaces --reset` clears the current project's.
# state_root=~/.local/state/claude-spaces
# Terminal pane height: characters (e.g. 15) or percentage (e.g. 35%)
# terminal_height=35%
# Terminal pane width (side orientation): characters or percentage
# terminal_width=45%
# Minimum Claude pane width when side terminal is open (characters)
# min_claude_width=10
# Minimum Claude pane height when bottom terminal is open (characters)
# min_claude_height=10
# Show jump index next to first 10 local sessions
# show_index=1
# Override tmux prefix key (default: inherited from tmux.conf)
# prefix=C-a
# Keybinding overrides (comma-separated for multiple keys)
# bind_terminal=`
# bind_side_terminal=Tab
# bind_toggle_terminal=t
# bind_focus_claude=Enter,i
# bind_nav_next=J,S-Down
# bind_nav_prev=K,S-Up
# bind_pane_left=h,Left,S-Left
# bind_pane_right=l,Right,S-Right
# bind_pane_up=k,Up
# bind_pane_down=j,Down
# bind_focus_picker=Space
# bind_detach=d
# bind_zoom=z
# bind_copy_mode=[
# bind_paste=]
# bind_search=/
# bind_new_session=c
# bind_fork=f
# bind_close=x
# bind_rename=r
# bind_rename_project=p
# bind_hide=H
# bind_shutdown=s
# bind_reload=R
# bind_refresh=+
# bind_menu=:Custom session names: ~/.local/share/claude-spaces/names (session_id=name;
respects $XDG_DATA_HOME)
Hidden sessions: ~/.local/share/claude-spaces/hidden (one ID per line;
respects $XDG_DATA_HOME)
