Skip to content
This repository was archived by the owner on Apr 12, 2026. It is now read-only.

cneller/tmux-assistant-resurrect

 
 

Repository files navigation

tmux-assistant-resurrect

Disclaimer: This project was entirely vibecoded (designed and implemented through conversation with AI coding assistants). It has been end-to-end tested in Docker with real CLI binaries (170+ automated tests + full save/kill/restore lifecycle smoke test), but has limited real-world usage so far. Expect rough edges. Contributions and bug reports welcome.

Persist and restore AI coding assistant sessions across tmux restarts and reboots.

Save, kill, and restore — assistant sessions resume automatically

When your computer shuts down, tmux sessions are lost -- including any running Claude Code, OpenCode, or Codex CLI instances. This project hooks into tmux-resurrect to automatically save assistant session IDs, CLI flags, and environment variables, then re-launch them with the exact same configuration after a restore.

How it works

SAVE (every 5 min + manual prefix+Ctrl-s)
  tmux-resurrect saves pane layouts
    -> post-save hook inspects child processes of each pane
    -> detects assistants by binary name (claude, opencode, codex)
    -> extracts session IDs via native hooks/plugins/process args
    -> writes ~/.tmux/resurrect/assistant-sessions.json

RESTORE (on tmux start or manual prefix+Ctrl-r)
  tmux-resurrect restores pane layouts
    -> post-restore hook reads assistant-sessions.json
    -> reconstructs full CLI invocation with saved flags + env vars
    -> sends resume commands to each pane, e.g.:
         ANTHROPIC_BASE_URL='...' claude --dangerously-skip-permissions --resume <id>
         opencode --verbose -s <session-id>
         codex --full-auto resume <session-id>

Design

Detection is done via direct process inspection: the save script takes a single ps snapshot of all processes, finds children of each tmux pane shell, and matches known assistant binary names (claude, opencode, codex).

Session ID extraction uses tool-native mechanisms (infrastructure plumbing):

Tool Primary method Fallback 1 Fallback 2 Notes
Claude Code SessionStart hook state file (keyed by Claude PID) --resume in process args - Claude overwrites its process title, so args fallback only works if args are visible
OpenCode -s / --session in process args Plugin state file SQLite DB query (~/.local/share/opencode/opencode.db) Go binary overwrites process title; DB fallback matches most recent session by cwd
Codex CLI PID lookup in ~/.codex/session-tags.jsonl resume in process args - Codex runs via Node.js, so args are always visible in ps

Each tool has a primary and fallback extraction method. Fallbacks address the chicken-and-egg problem: after a restore, session IDs are in process args even before hooks/plugins have fired. The OpenCode SQLite database fallback provides version-resilient session ID extraction even when the plugin hasn't fired.

Prerequisites

  • tmux (tested with 3.x)
  • TPM (Tmux Plugin Manager)
  • jq (used by save/restore scripts)
  • At least one of: Claude Code, OpenCode, Codex CLI

Installation

Install TPM if you don't have it:

git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

Add to your ~/.tmux.conf:

set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @plugin 'timvw/tmux-assistant-resurrect'

# Optional: restore terminal text in non-assistant panes after tmux restart.
# If enabled, the plugin automatically strips captured content for assistant
# panes so restore won't briefly flash stale TUI output before resuming.
# set -g @resurrect-capture-pane-contents 'on'

# Initialize TPM (must be last line)
run '~/.tmux/plugins/tpm/tpm'

Then inside tmux, press prefix + I (capital I). TPM will clone the plugins and automatically set up:

  • tmux-resurrect + tmux-continuum settings
  • Claude Code hooks in ~/.claude/settings.json
  • OpenCode session-tracker plugin in ~/.config/opencode/plugins/

Uninstallation

Remove the @plugin 'timvw/tmux-assistant-resurrect' line from ~/.tmux.conf, then press prefix + alt + u inside tmux.

Usage

Automatic (recommended)

Once installed, everything runs automatically:

  • tmux-continuum saves your tmux layout every 5 minutes
  • Post-save hook collects assistant session IDs at each save
  • On tmux server start, continuum auto-restores the layout
  • Post-restore hook resumes each assistant with its saved session ID

Manual save/restore keybindings (tmux-resurrect defaults):

Key Action
prefix + Ctrl-s Save tmux state + assistant sessions
prefix + Ctrl-r Restore tmux state + resume assistants

Repository structure

tmux-assistant-resurrect.tmux     # TPM plugin entry point
config/
  resurrect-assistants.conf       # tmux config reference template (not sourced automatically)
hooks/
  lib-claude-pid.sh               # Shared helper: walks process tree to find Claude PID
  claude-session-track.sh         # Claude SessionStart hook (writes session ID)
  claude-session-cleanup.sh       # Claude SessionEnd hook (removes state file)
  opencode-session-track.js       # OpenCode plugin (tracks session ID + cleanup)
scripts/
  lib-detect.sh                   # Shared library (detect_tool, pane_has_assistant, posix_quote)
  save-assistant-sessions.sh      # Resurrect post-save hook (process detection + session IDs)
  restore-assistant-sessions.sh   # Resurrect post-restore hook (resumes assistants)
test/
  Dockerfile                      # Docker image with tmux, jq, just, and real assistant CLIs
  run-tests.sh                    # Integration test suite
justfile                          # Install/uninstall/status/save/restore/test recipes

Testing

Automated tests (Docker)

The full test suite runs in Docker with real CLI binaries (no mocks):

just test

This builds a Docker image with tmux, jq, just, and the real @anthropic-ai/claude-code, opencode-ai, and @openai/codex npm packages, then runs the full test suite covering install, save, restore, uninstall, hooks, cleanup, TPM plugin installation, session ID extraction, POSIX quoting, process tree detection, upgrade-path migration, and regression scenarios. No API keys are needed — the tests exercise the process detection and session management layer, not the AI functionality.

Try it yourself

You can verify the full save → kill → restore cycle on your own machine using the normal TPM installation — no cloning or build tools needed.

Prerequisites: tmux, jq, and at least one of claude / opencode / codex installed.

1. Install

Follow the Installation steps above (install TPM, add the plugin lines to ~/.tmux.conf, press prefix + I inside tmux).

2. Launch some assistants

Start assistants in separate tmux windows or sessions — just like you normally would:

# In one tmux window:
cd ~/src/my-project
claude

# In another window:
cd ~/src/other-project
opencode

Work with them for a bit so the session hooks fire (Claude's SessionStart hook writes the session ID to disk automatically).

3. Save

Press prefix + Ctrl-s (the tmux-resurrect save keybinding). This saves the tmux layout and runs the assistant save hook, which detects running assistants and writes their session IDs to ~/.tmux/resurrect/assistant-sessions.json.

You can inspect what was saved:

cat ~/.tmux/resurrect/assistant-sessions.json | jq .

Example output:

{
  "timestamp": "2026-02-15T20:34:28Z",
  "sessions": [
    {
      "pane": "my-project:0.0",
      "tool": "claude",
      "session_id": "01abc...",
      "cwd": "/home/user/src/my-project",
      "pid": "12345",
      "model": "claude-opus-4-6",
      "cli_args": "--dangerously-skip-permissions --model claude-opus-4-6",
      "env": {"tmux_pane": "%1", "shell": "/bin/zsh", "ANTHROPIC_BASE_URL": "https://proxy.internal"}
    },
    {
      "pane": "other-project:0.0",
      "tool": "opencode",
      "session_id": "ses_xyz...",
      "cwd": "/home/user/src/other-project",
      "pid": "12346",
      "model": "",
      "cli_args": "",
      "env": {"tmux_pane": "%2", "shell": "/bin/zsh"}
    }
  ]
}

4. Kill tmux (simulate a reboot)

tmux kill-server

Everything is gone — all sessions, all panes, all running assistants.

5. Restore

Start tmux again:

tmux

Then press prefix + Ctrl-r (the tmux-resurrect restore keybinding).

tmux-resurrect recreates your sessions, windows, and panes. The post-restore hook then reads the saved assistant sessions and sends the correct resume command to each pane, preserving the original CLI flags and environment:

  • claude --dangerously-skip-permissions --model opus --resume <session-id>
  • opencode -s <session-id>
  • ANTHROPIC_BASE_URL='...' codex resume <session-id>

If the session was launched with flags like --dangerously-skip-permissions or --model, those flags are captured from ps at save time and replayed on restore. Environment variables configured via @assistant-resurrect-capture-env are prepended to the resume command.

6. Verify

Check the restore log to see what happened:

cat ~/.tmux/resurrect/assistant-restore.log

You should see lines like:

[2026-02-15T20:34:31Z] restoring 2 assistant session(s)...
[2026-02-15T20:34:31Z] restoring claude in my-project:0.0 (session: 01abc..., cmd: claude --dangerously-skip-permissions --resume '01abc...')
[2026-02-15T20:34:32Z] restoring opencode in other-project:0.0 (session: ses_xyz..., cmd: opencode -s 'ses_xyz...')
[2026-02-15T20:34:33Z] restored 2 of 2 assistant session(s)

The save log is also available if you want to see what was detected:

cat ~/.tmux/resurrect/assistant-save.log

Troubleshooting

Symptom Check
Save finds 0 sessions Run ps -eo pid=,ppid=,args= | grep -E 'claude|opencode|codex' to verify assistants are running
Session ID missing for Claude Verify the hook is installed: jq '.hooks.SessionStart' ~/.claude/settings.json
Session ID missing for OpenCode Launch with -s <id>, or verify the plugin: ls ~/.config/opencode/plugins/session-tracker.js
Restore launches but assistant says "session not found" The session ID may have expired. This is normal — start a fresh session and the next save will pick up the new ID
Assistants launch twice after restore Make sure assistants are not listed in @resurrect-processes — the plugin handles all resuming via the post-restore hook
just test fails with Docker errors Ensure Docker is running and you have network access (the image pulls npm packages)

Configuration

State directory

Session tracking files are written to a per-user temporary directory:

Platform Default path
Linux (systemd) $XDG_RUNTIME_DIR/tmux-assistant-resurrect (e.g., /run/user/1000/tmux-assistant-resurrect)
macOS $TMPDIR/tmux-assistant-resurrect (e.g., /var/folders/.../T/tmux-assistant-resurrect)
Fallback /tmp/tmux-assistant-resurrect (only if both XDG_RUNTIME_DIR and TMPDIR are unset)

This avoids permission conflicts on multi-user systems. Override with:

export TMUX_ASSISTANT_RESURRECT_DIR=/path/to/state

Note: state files are transient — they track running assistant PIDs and session IDs while tmux is active. The persistent sidecar JSON (~/.tmux/resurrect/assistant-sessions.json) is what survives reboots and lives in your home directory.

Environment variable capture and restoration

By default, the plugin captures TMUX_PANE and SHELL in each assistant's state file. To capture additional environment variables, set a space-separated list in tmux.conf:

set -g @assistant-resurrect-capture-env 'VIRTUAL_ENV NODE_ENV CONDA_DEFAULT_ENV'

Captured variables are stored in the state file's env object and propagated to assistant-sessions.json. On restore, variables listed in @assistant-resurrect-capture-env are prepended to the resume command:

VIRTUAL_ENV='/home/user/.venv' claude --resume <session-id>

Built-in variables (TMUX_PANE, SHELL) are not restored — TMUX_PANE would be stale after restore, and SHELL is already in the environment. State files live in a user-only directory (mode 0700).

Note: Avoid capturing secrets (API keys, tokens). State files and the sidecar JSON persist to disk and may outlive the process they were captured from.

Continuum save interval

Edit config/resurrect-assistants.conf:

set -g @continuum-save-interval '5'  # minutes

Adding support for a new assistant

To add a new AI coding assistant:

  1. Detection: Add a case pattern in detect_tool() in scripts/save-assistant-sessions.sh matching the tool's binary name
  2. Session ID extraction: Add a get_<tool>_session() function
  3. Restore command: Add a case branch in scripts/restore-assistant-sessions.sh with the tool's resume command
  4. Session tracking (optional): If the tool doesn't expose its session ID in process args or a known file, create a hook/plugin similar to the existing ones
  5. Update install/uninstall recipes in justfile if a new hook was added

How each component works

Claude Code hooks (hooks/claude-session-track.sh, hooks/claude-session-cleanup.sh)

Two hooks configured in ~/.claude/settings.json:

  • SessionStart: Claude Code passes JSON on stdin (including session_id, model, source, permission_mode, transcript_path, and more). The hook merges the full JSON payload with plugin metadata (tool, ppid, timestamp, env) and writes it to $STATE_DIR/claude-<PID>.json. This means any new fields Claude adds in future versions are captured automatically.
  • SessionEnd: Removes the state file when the Claude session exits, preventing stale entries.

Note: Claude Code sets process.title = 'claude', but on macOS arm64 (v2.1.44+) ps -eo args= still shows full args. The state file remains the primary source of session IDs, with process args as a fallback. CLI flags like --dangerously-skip-permissions are captured from ps by the save script's extract_cli_args() function.

OpenCode plugin (hooks/opencode-session-track.js)

An OpenCode plugin that listens for session.created, session.updated, and session.idle events. On each event, it captures the full session object (including model, title, and other metadata) along with init-time context (process.argv, client API surface) and writes it to $STATE_DIR/opencode-<PID>.json. This handles the case where a user switches sessions at runtime (via /sessions or Ctrl+x l). The plugin also cleans up its state file on process exit (SIGINT, SIGTERM).

Codex CLI

Codex natively writes PID-to-session mappings in ~/.codex/session-tags.jsonl. The save script reads this file directly -- no additional hook is needed.

Save hook (scripts/save-assistant-sessions.sh)

Runs after each tmux-resurrect save. Takes a single ps snapshot of all processes, finds children of each tmux pane's shell, and detects assistants by matching binary names. Then extracts session IDs using tool-specific methods (state files, process args, JSONL lookup). Also captures:

  • CLI flags (cli_args): extracted from ps args with the binary name and session/resume args stripped (e.g., --dangerously-skip-permissions --model opus)
  • Model (model): from state file (preferred) or --model in args (fallback)
  • Environment (env): from state file (captured by hooks/plugins)

Writes everything to ~/.tmux/resurrect/assistant-sessions.json.

Restore hook (scripts/restore-assistant-sessions.sh)

Runs after each tmux-resurrect restore. Reads the sidecar JSON and reconstructs the full CLI invocation for each assistant: <env_prefix> <binary> <cli_args> <resume_arg>. Sends the command to each pane via tmux send-keys. If enriched fields are missing (old-format JSON), falls back to bare resume commands.

Limitations

  • Running state is not preserved: Assistants restart with their conversation history loaded, but any in-flight tool calls or pending operations are lost.
  • First save after install (chicken-and-egg): On initial install, no session IDs exist yet. Assistants must complete at least one session (triggering the hooks) before their IDs can be saved. For Codex and OpenCode with -s, this is not an issue since session IDs are visible in process args.
  • Claude process title: Claude Code sets process.title = 'claude', but on macOS arm64 (v2.1.44+) ps -eo args= still shows full args. CLI flags like --dangerously-skip-permissions are captured from ps at save time. If a future version hides args, cli_args will be empty and restore falls back to bare resume commands.
  • OpenCode without plugin: If the OpenCode plugin isn't installed and the process was started without -s, the session ID cannot be detected.
  • OpenCode DB fallback (same-cwd ambiguity): When the plugin state file is unavailable and no -s flag was used, the save script falls back to the OpenCode SQLite database, matching sessions by working directory. If multiple sessions share the same cwd, the most recently updated one is picked — which may not be the correct one for that specific pane.
  • Process inspection on macOS: Uses ps -eo pid=,ppid= instead of pgrep -P due to reliability issues with pgrep on macOS.
  • Pane matching after restore: tmux-resurrect preserves pane indices, so the restore hook targets the same session:window.pane addresses. If you manually rearrange panes between save and restore, the mapping may be wrong.

License

MIT

About

Persist and restore AI coding assistant sessions (Claude Code, OpenCode, Codex CLI) across tmux restarts

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Shell 85.2%
  • Just 11.6%
  • JavaScript 2.4%
  • Dockerfile 0.8%