Codex Tmux Manager — survive SSH drops, reattach from your phone.
Quickstart · Commands · Mobile · Config
Download the prebuilt binary for your platform (no Go toolchain needed):
# Linux x86_64 — adjust URL for your OS/arch (see below).
curl -LO https://github.com/RandomCodeSpace/ctm/releases/latest/download/ctm-$(curl -s https://api.github.com/repos/RandomCodeSpace/ctm/releases/latest | jq -r .tag_name)-linux-amd64.tar.gz
tar xzf ctm-*-linux-amd64.tar.gz && sudo mv ctm-*/ctm /usr/local/bin/
ctm # launches tmux + hermes (default agent); drop SSH, reattach anytime
ctm new --agent codex # opt back into codex per-session
ctm last # one-word reconnect from your phoneOr with Go installed:
go install github.com/RandomCodeSpace/ctm@latestEither way, ctm bootstraps ~/.config/ctm/ on first run and injects shell aliases into ~/.bashrc / ~/.zshrc if they exist.
Codex on a remote dev box is great until your train enters a tunnel. Plain SSH + a direct codex invocation dies with the connection; reconnecting starts from scratch. ctm wraps codex in tmux with mobile-first defaults — Alt-based keybindings, OSC52 clipboard, one-keystroke session pickers, stale-session markers — so the conversation keeps running while you're underground, and reattaches from your phone with a single word.
codex 0.130.0 ~/projects/ctm ●
(Above: ctm's tmux statusline — agent + cwd + activity dot. Codex doesn't expose context/rate-limit telemetry, so the line stays minimal.)
- Mobile-first workflow.
ctm last,ctm pick <filter>,Alt-asecond prefix, OSC52 clipboard sync, stale-session markers — the entire UX assumes you're on a phone with flaky Wi-Fi and a fat thumb. - Persistent sessions. tmux-backed. Codex keeps running when SSH drops; reattach from any device.
- Resume with fallback.
codex resume <id> || codex— recovers cleanly when the prior session can't be re-opened. Usecodex resume --lastfor the most recent. - YOLO mode. Auto-commits a git checkpoint before launching with
codex --sandbox danger-full-access, so you can always roll back. - Preflight health checks. Env vars, PATH, workdir, tmux session, codex process — cached for 60 s to keep mobile reconnects snappy.
- Tight lifecycle coupling. When codex exits, the tmux session dies. No stuck bash shells, no zombie tabs.
- Multi-agent. Hermes Agent is the default; pass
--agent codextoctm neworctm yoloto spawn codex instead. New agents plug in viainternal/agent.Registerwithout touching call sites. - Crash-safe state. Atomic writes, flock-based locking, strict JSON decode with self-healing strip-to-.bak,
schema_version+ startup migrations onsessions.json/config.json. - Zero non-tmux runtime deps. Pure Go throughout. No
jq,pgrep,grep, oruuidgenrequired.
Grab the archive for your platform from the latest release, extract, and drop ctm into a directory on your $PATH:
| Platform | Asset |
|---|---|
| Linux x86_64 | ctm-<version>-linux-amd64.tar.gz |
| Linux ARM64 | ctm-<version>-linux-arm64.tar.gz |
| macOS (Intel) | ctm-<version>-darwin-amd64.tar.gz |
| macOS (Apple Silicon) | ctm-<version>-darwin-arm64.tar.gz |
Every asset is accompanied by a SHA256SUMS file; verify with sha256sum -c SHA256SUMS. Windows users: run the Linux binary under WSL — tmux has no native Windows support.
go install github.com/RandomCodeSpace/ctm@v0.1.0Replace v0.1.0 with any tag from the releases page. Every release includes the exact go install command for that version in its release notes.
go install github.com/RandomCodeSpace/ctm@latestNo extra setup step is required — the first time you run any agent-launching command (ctm, ctm <name>, ctm new, ctm yolo), ctm bootstraps ~/.config/ctm/ with sensible defaults, regenerates tmux.conf on every launch, and injects shell aliases into ~/.bashrc / ~/.zshrc if they exist.
If you prefer an explicit setup step (or want the cc-session migration to run), ctm install still does the same work upfront.
Lifecycle hooks — fire shell commands on on_attach / on_new / on_yolo / on_safe / on_kill
ctm can fire a user-supplied shell command on five lifecycle events. Declare them under hooks in ~/.config/ctm/config.json:
{
"hooks": {
"on_attach": "notify-send 'attached to $CTM_SESSION_NAME'",
"on_new": "echo \"$(date -Iseconds) new $CTM_SESSION_NAME\" >> ~/.ctm-audit.log",
"on_yolo": "curl -s -X POST https://hooks.example/ctm/yolo -d name=$CTM_SESSION_NAME",
"on_safe": "echo safe $CTM_SESSION_NAME",
"on_kill": "tar czf ~/session-snapshots/$CTM_SESSION_NAME-$(date +%s).tgz -C $CTM_SESSION_WORKDIR ."
},
"hook_timeout_seconds": 5
}Each command runs through sh -c with the following env vars:
| Var | Value |
|---|---|
CTM_EVENT |
event name (e.g. on_attach) |
CTM_SESSION_NAME |
session name |
CTM_SESSION_UUID |
session UUID |
CTM_SESSION_MODE |
safe or yolo |
CTM_SESSION_WORKDIR |
absolute workdir |
Hooks run synchronously with a per-hook wall-clock ceiling (default 5 s, override via hook_timeout_seconds). Failures log a WARN-level slog line and are otherwise ignored — they never block the action that triggered them. For fire-and-forget semantics, append & inside the shell command.
Shell completion — bash / zsh / fish / powershell
ctm completion [bash|zsh|fish|powershell] emits a completion script on stdout. Install per shell:
# bash — system-wide
ctm completion bash | sudo tee /etc/bash_completion.d/ctm >/dev/null
# bash — per-user (append to ~/.bashrc)
echo 'source <(ctm completion bash)' >> ~/.bashrc
# zsh — assumes fpath is set and there is a completion dir in it
ctm completion zsh > "${fpath[1]}/_ctm"
# fish
ctm completion fish > ~/.config/fish/completions/ctm.fish
# powershell — session only
ctm completion powershell | Out-String | Invoke-ExpressionCompletion is aware of subcommands, flags, and (for ctm attach, ctm kill, ctm rename, etc.) live session names pulled from ~/.config/ctm/sessions.json.
- tmux 3.0+
- Codex CLI on
$PATH(install vianpm i -g @openai/codexor your package manager of choice) - A terminal that speaks xterm + OSC52 (Termius, WebSSH, iTerm2, Kitty, wezterm, Windows Terminal)
- Go 1.25+ — only if you build from source (
go install); prebuilt binaries have no Go dependency - Linux or macOS — Windows is not supported natively; use WSL
| Command | Description |
|---|---|
ctm |
Attach to the default session (hermes). Creates it if missing. |
ctm <name> |
Attach to a named session, or create it. |
ctm cc |
Shorthand for attaching to cc. |
ctm new <name> |
Create a new session in a specific workdir. |
ctm yolo [name] |
Create/attach a YOLO session (permissions bypassed + git checkpoint). |
| Command | Description |
|---|---|
ctm last (alias l) |
Attach to the most recently used LIVE session. Mobile reconnect in one word. |
ctm pick [filter] (alias p) |
Interactive session picker. With filter, narrows to substring match; single match auto-attaches. Inside tmux uses the native choose-session. |
ctm switch <name> (alias sw) |
Switch to a named session (uses switch-client inside tmux). |
ctm ls (alias list) |
List all sessions with mode, live status, age, idle time, and [STALE] markers for sessions idle > 7 days. |
| Command | Description |
|---|---|
ctm detach |
Detach the current tmux client. Same as Alt-d inside a session. |
ctm kill <name> |
Kill a tmux session and its codex process. |
ctm forget <name> |
Remove a session from the store without killing tmux. |
ctm rename <old> <new> |
Rename a session across ctm state and tmux. |
| Command | Description |
|---|---|
ctm check |
Run preflight health checks (exits non-zero on failure). |
ctm doctor |
Show detailed environment, session state, and suggested fixes. |
ctm --verbose <cmd> |
Emit debug output for any command (alias for --log-level=debug). |
ctm --log-level <lvl> |
Structured diagnostic log level on stderr: debug|info|warn|error. Default: info. Set CTM_LOG_FORMAT=json for NDJSON output. |
ctm version |
Print version. |
Inside any ctm tmux session:
| Key | Action |
|---|---|
Ctrl-b |
Default tmux prefix |
Alt-a |
Mobile-friendly second prefix |
Alt-[ |
Enter copy mode (no prefix needed) |
Alt-d |
Detach client |
Ctrl-b [ |
Enter copy mode (standard tmux) |
The mobile scrollback trick. Codex's TUI uses alt-screen and has no built-in scroll history. To scroll back on a phone:
- Press
Alt-[(orCtrl-b [) — enters tmux copy mode.- Swipe / arrow keys to scroll.
qto exit.
Termius / WebSSH users: Wire Alt-[ to a one-tap icon with a Snippet.
| Field | Value |
|---|---|
| Name | scroll |
| Content | <M-[> (Alt-[) |
| Assign icon | any — tap it for instant copy mode |
~/.config/ctm/config.json— main config (scrollback lines, required env vars, default mode, health check timeout, yolo checkpoint toggle)~/.config/ctm/sessions.json— session state (atomically written, flock-locked)~/.config/ctm/tmux.conf— generated tmux config (mobile-optimized, don't edit)
config.json and sessions.json carry a top-level "schema_version" integer. On every startup ctm runs a migration pass: if a file is below the current schema version, the migrator applies pending steps, stamps the new version, and writes atomically. Before any destructive migration write, the original bytes are copied to <path>.bak.<unix-nano> — recovery is always one mv away. A newer-than-known schema_version causes a hard refusal to start rather than silent downgrade. Missing files are left untouched; the migrator never creates them.
go install github.com/RandomCodeSpace/ctm@latestThen regenerate the tmux config to pick up any new defaults:
rm ~/.config/ctm/tmux.conf
ctm ccMIT