Terminal UI to navigate, resume, and start Claude Code and Codex sessions
Project description
Railmux — session manager for Claude Code & Codex
A terminal UI to navigate, resume, and start Claude Code and Codex sessions across all your projects. Railmux lives in the left pane of a tmux window; the right pane shows the active agent. Each session runs in its own detached tmux session, so switching never interrupts in-progress responses or tool calls.
- Claude Code mode — reads
~/.claude/projects/*, lists sessions by project, resume withclaude --resume - Codex mode — reads
~/.codex/sessions/*, same sidebar workflow for Codex sessions - Press
mto cycle through the available modes
Demo
Demo GIF coming soon. This slot is reserved for the interactive Railmux walkthrough and can be replaced without restructuring the README.
Why Railmux?
Without Railmux, managing multiple agent sessions means manually tracking tmux windows, remembering which session lives where, and copy-pasting session IDs. Sessions pile up across projects, context gets lost, and switching between them is friction.
Railmux replaces all of that with a single keystroke:
- One sidebar, all sessions — browse every Claude Code and Codex session across every project, filter by name, star favourites
- Instant switching — press Enter and the right pane attaches to a different background tmux session; every agent keeps running, no responses lost
- Zero manual bookkeeping — no more
tmux ls | grep cc-or hunting through~/.claude/projects/
Quick start
pip install railmux
# or: pip3 install railmux
railmux
Requires Python 3.9+, tmux, less, and at least one supported agent CLI on
PATH. Claude Code and Codex are independent: a missing provider does not stop
you from using the other one.
If tmux is missing, an interactive Railmux launch can offer to install it
with Homebrew on macOS or apt-get on Debian/Ubuntu/WSL. Railmux shows the
exact command and requires explicit confirmation (default: no); it never
installs Homebrew itself or modifies the system during non-interactive runs.
Other common Linux package managers receive a copyable installation command.
If you are not already inside tmux, Railmux launches its own tmux session. Run
railmux --doctor for a privacy-safe dependency and environment report when
setup does not behave as expected.
Keys
Navigation
| Key | Action |
|---|---|
↑ / ↓ |
Move selection within the focused pane |
Tab / Shift-Tab |
Cycle focus through Projects, Sessions, Running panes |
Esc |
Move focus up: Running → Sessions → Projects |
/ |
Filter the focused Projects, Sessions, or Running pane by name |
Session actions
| Key | Action |
|---|---|
Enter |
Resume or start the selected session |
n |
Start a fresh session in the current project |
i |
Popup with session details |
r |
Rename the focused session |
s |
Toggle star — starred sessions pinned to top with ⭐ |
k |
Kill the running agent process (keeps session file) |
d |
Delete the focused session (prompts for confirmation) |
t |
Open a terminal in the active project directory |
m |
Cycle through available agent modes |
F9 |
Fullscreen the agent pane (toggle) for clean text selection |
? |
Full help popup with all keybindings |
q or Ctrl-C |
Quit with confirmation |
+ New project works in both Claude Code and Codex modes. Browse to an
existing directory and choose . (use this path), or type a new relative,
absolute, or ~-based path. When no existing entry matches, select the
explicit + create … row (it is focused automatically) and press Enter;
railmux creates the directory before starting the agent.
When a mode has no projects or sessions, its empty state names the active
provider and points to + New project or n, so an unavailable provider never
looks like data from the previous mode.
Running-pane filtering is in-memory and never reads transcript bodies. Plain
text matches the visible session label, project, and provider; add
project:<name> to restrict a search to one project. Claude Code and Codex keep
independent Running filters. Blocked sessions move ahead of other Running rows
on the existing throttled recency sort, while their red status dots still
update immediately.
Mouse
| Action | Effect |
|---|---|
| Left-click (non-running) | Preview session history in the right pane |
| Left-click (running) | Attach to the running session (focus stays left) |
| Double-click | Open/attach and move focus to the right pane |
| Right-click | Context menu (Open, Info, Rename, Star, Kill, Term, Delete) |
History preview
Left-click a non-running session to view its conversation history in the right pane without starting or resuming the agent. The preview reads the saved JSONL directly, so it cannot send a message or mutate the session. It follows the providers' conversation structure as closely as their saved data allows: user/assistant messages, tool calls and abbreviated tool output are colour-coded, while internal system context and encrypted reasoning are hidden. Plaintext Codex reasoning summaries are shown when present.
The viewer uses less, the standard pager available on both Linux and macOS,
because neither provider currently exposes a native read-only history view.
It opens at the latest activity and streams at most the latest 2,000 JSONL
records for fast previews of large sessions. Press / to search, n/N to
move between matches, and q to exit; the right pane restores whatever was
there before. Shell/editor commands and pager history are disabled in preview
mode. Double-click to skip the preview and open the session directly.
Clicking a running session attaches to it immediately (focus stays left so you can keep browsing). Double-clicking steals focus to the right pane for both running and non-running sessions.
Status indicators
Each running session shows a coloured ● reflecting its current state:
- Green — idle (assistant last responded normally)
- Yellow — busy (assistant is processing)
- Red — blocked (waiting for tool approval)
An independent magenta ! marks the last provider outcome that still needs
attention, such as a generic abort or provider error. It does not replace the
activity dot: a live session can be idle and still show !, while a stopped
historical session keeps its neutral ○ liveness marker alongside the badge.
Session Info and Running Info show the sanitized category and summary.
A grass-green title identifies a live tmux session independently of its status; stopped sessions use a neutral hollow ○. The same grass green is used for the focused pane chrome and tmux status bar. The current cursor uses a deeper green background, while the session displayed in the agent pane remains marked in neutral slate after keyboard focus moves away.
For Codex rollouts with lifecycle events, only task_complete, turn_aborted,
or thread_rolled_back ends an active turn; intermediate assistant messages and
tool results remain busy. Older rollouts without lifecycle records fall back to
their last user/assistant message. Because Codex has no reliable approval-wait
signal, a pending tool must remain unchanged for two minutes before it turns
red, reducing false alerts from normal long-running commands.
Codex error details are read only from dedicated lifecycle/error records, never from user or assistant transcript text. Current observed rollouts do not persist a reliable capacity/rate-limit reason, so Railmux labels those records generically instead of guessing from message content.
How it works
railmux reads agent session files from ~/.claude/projects/* (Claude Code) or ~/.codex/sessions/* (Codex) and lists everything. Pressing Enter on a session does two things: (1) if a detached tmux session for that session doesn't already exist, railmux creates one with tmux new-session -d; (2) railmux's right pane displays it so you see and interact with the agent. By default Railmux transactionally swaps the real agent pane into the display window, while its detached home session stays alive behind a placeholder. Unsupported or unverified environments automatically use the compatibility nested-tmux display instead.
Soft quit keeps detached agents alive. On restart, process-bearing recovery state is isolated to the exact outer tmux pane, so separate Railmux windows or private tmux servers cannot restore one another's display. Mode, project, and sidebar filters are also saved as non-process view preferences for use on a later login; those portable preferences never authorize an attach or kill. If Railmux stops while a new provider is still creating its session UUID, the Running pane restores it as an explicit unresolved entry. It can be reopened or stopped by exact tmux identity, but Railmux will not guess at or delete an unknown provider history file.
Configuration
Optional config at ~/.config/railmux/config.toml:
[claude]
# Path to the claude binary (default: "claude")
binary = "claude"
[codex]
# Path to the codex binary (default: "codex")
binary = "codex"
home = "~/.codex"
[projects]
# Show projects with no resumable sessions (default: false)
show_empty_projects = false
[live]
# How often to refresh the session list (ms)
poll_interval_ms = 1000
# Prefer the real agent pane through validated transactional tmux swaps.
# Default: "swap". Set "nested" to force the compatibility display.
agent_transport = "swap" # or "nested"
The swap transport requires tmux 2.7 or newer and the auto-launched railmux
tmux session. Railmux automatically falls back to nested for old tmux,
unmanaged sessions, independent clients, unsupported agent topology, or any
unverified transition. See
docs/DENESTED_AGENT_PANE.md for tested
lifecycle behavior, fallbacks, performance observations, and limitations.
Diagnostics
railmux --doctor
The doctor command works even when tmux is missing. It reports component
versions, terminal capability hints, configuration health, and whether provider
data directories are accessible. Its output is designed for issue reports: it
does not include hostnames, usernames, session IDs, transcripts, credentials,
environment values, configured commands, or raw custom paths.
FAQ
1. How do I copy text from the agent pane?
Under tmux the sidebar and agent share the screen, and over SSH your clipboard lives on the local machine.
OSC 52 (iTerm2, kitty, WezTerm, Alacritty, foot, Windows Terminal): drag-select in the agent pane copies to the local clipboard automatically, even over SSH, no Shift needed. (iTerm2: enable Settings → General → Selection → "Applications in terminal may access clipboard".)
Without OSC 52 (Terminal.app, etc.): press F9 to fullscreen the agent →
Shift‑drag to select → Cmd+C / Ctrl+C to copy → F9 to return.
Ctrl-B zalso toggles fullscreen (built into tmux) but zooms whichever pane has focus — it may fullscreen the sidebar instead of the agent.
2. Mouse clicks or F9 don't work — what's wrong?
These are usually terminal‑side settings, not tmux or railmux.
Mouse: your terminal must forward mouse events to tmux. Check your terminal's
profile settings for "Report mouse events" or "Mouse reporting" and make sure
it's enabled. tmux's set -g mouse on should already be in place (railmux
sets it for its own session).
F9 (fullscreen): on macOS, F9 is often captured by Mission Control.
Fix: System Settings → Keyboard → "Use F1, F2, etc. keys as standard function
keys". On Windows laptops the Fn‑lock key (Fn+Esc) toggles function‑key
behaviour. If your terminal has a "Pass function keys to terminal" option,
enable it.
3. Using railmux over SSH
railmux works over SSH out of the box — the scroll‑agent integration is SSH‑transparent, so mouse scrolling in the agent pane works the same as locally. These tweaks improve responsiveness and scrollback:
Server (~/.tmux.conf on the remote machine):
set -sg escape-time 0 # eliminate delay after Escape key
set -g history-limit 10000 # generous scrollback per pane
Client (~/.ssh/config on your local machine):
Host your-server
Compression yes # smoother tmux pane scrolling over SSH
If the connection is so slow that the sidebar can't refresh one frame per
second, skip the mouse and use keyboard navigation — ↑↓ / Tab / Enter
cover every operation and don't depend on a fast redraw.
4. Will automated review sessions pollute my session list?
Codex: sessions created by codex exec (headless automation, pre‑commit
hooks, CI) are filtered automatically — railmux only shows interactive
sessions (codex-tui, codex_cli_rs).
Claude Code: for one-shot automated reviews, disable session persistence so
the consultation never appears in /resume:
# Print mode only; the review is not saved as a resumable session
claude -p --no-session-persistence "review this diff"
(--no-session-persistence is a Claude Code print-mode option.)
5. pip reports "externally-managed-environment"
Create a virtual environment, then install Railmux with that environment's
pip. This works on macOS, Linux, and WSL without modifying the system Python:
python3 -m venv ~/.venvs/railmux
source ~/.venvs/railmux/bin/activate
pip install railmux
pipx install railmux is an optional convenience for a globally available CLI;
it is not required on macOS or any other platform.
Acknowledgements
The tmux sidebar idea and initial architecture came from regmi-saugat/ccmgr. railmux extends it with Codex support, session history preview, starring, in-app renaming, mouse interaction, and a status bar integrated into the tmux status line.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file railmux-0.1.2.tar.gz.
File metadata
- Download URL: railmux-0.1.2.tar.gz
- Upload date:
- Size: 287.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a8f8915a9d51fe31c64c9703fc541c4551d8a4985f371f5c22a26132dd8f5169
|
|
| MD5 |
4d11c90dfbffd6cb93de061e8295f397
|
|
| BLAKE2b-256 |
cfd70056fdbb9d811c99f5b7500c36183a27746a540959eb2bf275c3bdc28a85
|
Provenance
The following attestation bundles were made for railmux-0.1.2.tar.gz:
Publisher:
release.yml on Rightglow/Railmux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
railmux-0.1.2.tar.gz -
Subject digest:
a8f8915a9d51fe31c64c9703fc541c4551d8a4985f371f5c22a26132dd8f5169 - Sigstore transparency entry: 2191712970
- Sigstore integration time:
-
Permalink:
Rightglow/Railmux@da5a6f9db2c9ffbc41de008f568c26ad9fac030c -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Rightglow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@da5a6f9db2c9ffbc41de008f568c26ad9fac030c -
Trigger Event:
push
-
Statement type:
File details
Details for the file railmux-0.1.2-py3-none-any.whl.
File metadata
- Download URL: railmux-0.1.2-py3-none-any.whl
- Upload date:
- Size: 182.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46e53e559cc275bae2f1ff35c583a5c0d0c38028bede9bb84dada105a066edc6
|
|
| MD5 |
f0de1d0c13e72f6acfb301ff62fff667
|
|
| BLAKE2b-256 |
3072d3586634455043818113c535775edc1e0cf1aed7deee1d6034abd80bf32d
|
Provenance
The following attestation bundles were made for railmux-0.1.2-py3-none-any.whl:
Publisher:
release.yml on Rightglow/Railmux
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
railmux-0.1.2-py3-none-any.whl -
Subject digest:
46e53e559cc275bae2f1ff35c583a5c0d0c38028bede9bb84dada105a066edc6 - Sigstore transparency entry: 2191712987
- Sigstore integration time:
-
Permalink:
Rightglow/Railmux@da5a6f9db2c9ffbc41de008f568c26ad9fac030c -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Rightglow
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@da5a6f9db2c9ffbc41de008f568c26ad9fac030c -
Trigger Event:
push
-
Statement type: