A
Persistent Sessions
Server Setup Guide v1.0
System: Session Persistence

Persistent Claude Sessions

Keep Claude Code running while you switch devices, close your laptop, or walk away. Start on your desktop. Check in from your phone. Pick up right where you left off.

dashboard.yourdomain.com — tmux: main
$ claude
# Working on a long research task...
# Close the browser tab. Restart your PC.
# Open cockpit from your phone...

# .bashrc drops you into the same tmux session
$ # Claude is still running. Exactly where you left it.
[main] 1:claude* 2:logs 3:shell 14:23 Feb 20

The problem

If you run Claude Code in a browser-based terminal — like Cockpit — your session lives inside a WebSocket connection. When that connection drops, everything dies.

Close the browser tab. Lock your phone. Let your laptop sleep. Lose wifi for 30 seconds. Claude stops mid-task. Your context is gone. You start over.

The timeout problem

Cockpit terminates idle web sessions after 15 minutes by default. If Claude is quietly running tools and you're not clicking in the UI, the session gets killed automatically.

The disconnect problem

Without tmux, Claude is a child process of the browser terminal. Connection drops → shell dies → Claude dies. No recovery. No way to reconnect to the same session.

The solution: tmux

tmux is a terminal multiplexer that runs on the server, not in your browser. Your Claude session lives inside tmux. The browser terminal is just a viewport — a window into a room that exists whether or not you're looking at it.

// without tmux
Browser → Cockpit WebSocket → bash → claude
Tab closes → WebSocket dies → bash dies → Claude dies
// with tmux
Browser → Cockpit WebSocket → tmux (server-side)
└─ window 1: claude (keeps running)
└─ window 2: logs
└─ window 3: shell
Tab closes → WebSocket dies → tmux keeps running
Reconnect from any device → reattach → Claude still there

You can attach to the same tmux session from desktop, laptop, and phone simultaneously. All see the same terminal, live.

Setup

1

Configure tmux

Create ~/.tmux.conf on your server. Enables mouse support, readable status bar, 10k-line scrollback, and a built-in help popup at Ctrl+B H.

~/.tmux.conf
# True color + mouse support
set -g default-terminal "screen-256color"
set -g mouse on

# Window numbering from 1, auto-renumber
set -g base-index 1
set -g pane-base-index 1
set-option -g renumber-windows on

# More scrollback
set -g history-limit 10000

# Status bar
set -g status-style "bg=#0d1117,fg=#8b949e"
set -g status-left "#[fg=#3fb950,bold] [#S] "
set -g status-right "#[fg=#484f58]Ctrl+B H for help  #[fg=#3fb950]%H:%M #[fg=#484f58]%b %d"
set -g window-status-current-style "fg=#3fb950,bold"
set -g pane-active-border-style "fg=#3fb950"

# Ctrl+B H — quick reference popup (requires tmux 3.2+)
bind H display-popup -E -w 62 -h 26 "cat << 'EOF'

  tmux quick reference

  Ctrl+B C       new window
  Ctrl+B ,       rename window
  Ctrl+B W       list all windows
  Ctrl+B 1-9     switch to window
  Ctrl+B N / P   next / previous window
  Ctrl+B %       split left/right
  Ctrl+B \"       split top/bottom
  Ctrl+B arrows  move between panes
  Ctrl+B Z       zoom pane (toggle)
  Ctrl+B [       scroll mode (Q to exit)
  Ctrl+B D       detach (session keeps running)
  Ctrl+B H       this help

EOF
read -n1 -s -r -p '  press any key to close'"

Requires tmux 3.2+ for the popup. Check with tmux -V. Remove the bind H block for older versions.

2

Auto-attach on terminal open

Add this to ~/.bashrc. It detects the Cockpit environment via XDG_SESSION_TYPE=web and attaches to (or creates) a tmux session named main.

~/.bashrc
# Auto-attach to persistent tmux session in cockpit terminals.
# XDG_SESSION_TYPE=web is set by cockpit, not SSH or a physical console.
# new-session -A creates 'main' or attaches if it already exists.
if [ -z "$TMUX" ] && [ "$XDG_SESSION_TYPE" = "web" ]; then
    exec tmux new-session -A -s main
fi
Using SSH instead of Cockpit?

Replace the condition with [ -n "$SSH_TTY" ]. This fires only for interactive SSH logins — not for automated ssh host "command" calls that don't allocate a TTY.

3

Disable the Cockpit idle timeout

Cockpit kills idle web sessions after 15 minutes — exactly long enough to time out while Claude is working quietly. Disable it:

/etc/cockpit/cockpit.conf
[Session]
IdleTimeout=0
terminal
sudo systemctl restart cockpit.service

Key bindings

All tmux commands use a prefix: press Ctrl+B, release it, then press the next key. It's a sequence, not a chord. Ctrl+B H shows a popup cheatsheet in your terminal.

Windows
Ctrl+B CNew window
Ctrl+B ,Rename current window
Ctrl+B WList all windows (interactive picker)
Ctrl+B 19Switch to window by number
Ctrl+B N / PNext / previous window
Panes
Ctrl+B %Split left/right
Ctrl+B "Split top/bottom
Ctrl+B ↑↓←→Move between panes
Ctrl+B ZZoom pane to full screen (toggle)
Scroll & session
Ctrl+B [Scroll mode — arrows or PgUp, press Q to exit
Ctrl+B DDetach — session keeps running on the server
Ctrl+B HHelp popup (from this config)
Ctrl+B ?Full built-in keybinding list

Suggested window layout

Name your windows with Ctrl+B , so the status bar tells you what's running at a glance.

[main] 1:claude*

1: claude

Your active Claude Code session. This is where you work.

[main] 2:logs*

2: logs

journalctl -f or tail -f on scheduler or app logs.

[main] 3:shell*

3: shell

Free shell for quick commands, file checks, or builds.

Coexisting with scheduled sessions

If you run automated Claude sessions on a cron schedule, they can conflict with your interactive session — competing for CPU and processing the same data. Add a guard to skip automated wakes when you're actively working:

claude-scheduler.py
def is_interactive_session_active():
    """Skip automated wakes when the user has an active Claude session."""
    result = subprocess.run(
        ['tmux', 'has-session', '-t', 'main'], capture_output=True
    )
    if result.returncode != 0:
        return False  # 'main' session doesn't exist

    result = subprocess.run(
        ['tmux', 'list-panes', '-t', 'main', '-F', '#{pane_current_command}'],
        capture_output=True, text=True
    )
    return 'claude' in result.stdout.lower()

# In your run_task() function, before spawning:
if is_interactive_session_active():
    print("Skipping: interactive Claude session is active")
    return False

Getting notified when input is needed

tmux can highlight a tab when the process in it goes quiet — a reliable signal that something finished and is waiting for you. This uses monitor-silence, which fires after a window produces no output for a set number of seconds.

Add to ~/.tmux.conf:

~/.tmux.conf
# Light up tabs that have gone quiet (waiting for input)
set-window-option -g monitor-silence 20
set -g visual-silence on
# Also pass terminal bell alerts through
set -g monitor-bell on
set -g visual-bell on
set -g bell-action any

# Show ~ in tab name when silent, ! on bell
set -g window-status-format "  #I:#W#{?window_silence_flag, ~,}#{?window_bell_flag, !,}  "

How the indicators work

2:claude ~ Window 2 has been silent for 20+ seconds — likely at a prompt or waiting for input
2:claude ! Window 2 rang the terminal bell — explicit program-triggered alert

Why silence, not activity? monitor-activity fires on every byte of output — too noisy when Claude is actively streaming. monitor-silence fires only when output stops. While a session runs it produces continuous output; when it finishes or hits a prompt, it goes quiet. That's the signal you actually want.

Common pitfalls

Watch out

The first session after setup won't be in tmux

The .bashrc change only applies to new terminals. Close your current terminal and open a fresh one to get the auto-attach behavior.

Watch out

display-popup requires tmux 3.2+

Check with tmux -V. Remove the bind H display-popup ... block for older versions and use Ctrl+B ? instead.

Best practice

Name your windows

Use Ctrl+B , to name each window. Without names, the status bar shows a row of identical bash entries.

Best practice

tmux survives disconnects, not reboots

Sessions persist across browser drops and network hiccups — not server reboots. A Raspberry Pi or always-on VPS is ideal. A laptop you shut down is not.