Wrap each agent in a tmux session for persistence

Author: model-collapseCreated Feb 25, 2026Updated May 27, 2026
Labelstype: featurearea: host

Problem

When using VS Code Remote (SSH, WSL, etc.), closing a terminal kills the Claude process immediately. There's no way to disconnect from a remote session and reconnect later without losing all running agents. This is especially painful for long-running tasks where Claude is actively working.

Solution

Each agent now runs inside a tmux session when tmux is available. This means:

  • Closing a terminal only detaches from tmux -- Claude keeps running in the background
  • Detached agents stay visible in the office with a semi-transparent appearance and gray X bubble
  • Clicking a detached agent reattaches by opening a new terminal connected to the tmux session
  • Orphaned sessions are auto-discovered on startup, so reopening VS Code reconnects to agents that were left running
  • Graceful fallback -- when tmux is not installed, everything works exactly as before

Changes

New File

  • src/tmuxManager.ts -- Pure functions for all tmux shell interactions: availability check (cached), session naming, list/check/kill sessions, build attach/new-session commands

Backend

File Changes
src/constants.ts Added TMUX_SESSION_PREFIX, TMUX_HEALTH_CHECK_INTERVAL_MS
src/types.ts terminalRef now nullable, added tmuxSessionName and isDetached to AgentState/PersistedAgent
src/agentManager.ts Launch wraps in tmux; persist/restore handles detached state; new discoverOrphanedTmuxSessions(); sendExistingAgents() notifies webview of detached agents
src/PixelAgentsViewProvider.ts Terminal close = detach (if tmux alive); focus = reattach; close = kill tmux; 5s health check timer for dead sessions; null terminalRef guards
src/fileWatcher.ts Guarded null terminalRef in terminal adoption code
src/extension.ts Dev build activation log

Webview

File Changes
webview-ui/src/constants.ts DETACHED_CHARACTER_ALPHA = 0.45
webview-ui/src/office/types.ts Added isDetached to Character, extended bubbleType with 'detached'
webview-ui/src/office/engine/characters.ts Default isDetached: false in createCharacter
webview-ui/src/office/engine/officeState.ts New setAgentDetached() method
webview-ui/src/hooks/useExtensionMessages.ts Handles agentDetached / agentReattached messages
webview-ui/src/office/engine/renderer.ts Detached characters at 45% opacity; renders detached bubble sprite
webview-ui/src/office/sprites/spriteData.ts New BUBBLE_DETACHED_SPRITE (gray X symbol)
webview-ui/src/office/components/ToolOverlay.tsx Shows "Detached" status text and gray dot
webview-ui/src/index.css Added --pixel-status-detached CSS variable

How It Works

Terminal open:    VS Code Terminal  <-->  tmux session  <-->  claude process
Terminal closed:  (detached)              tmux session  <-->  claude process  (still running!)
Reattach:         New Terminal     <-->  tmux session  <-->  claude process

tmux session names follow the pattern pixel-agents-{agentId}-{sessionUuid}, which allows the extension to discover and adopt orphaned sessions by matching the UUID to JSONL transcript files.

Edge Cases Handled

  • tmux not installed: isTmuxAvailable() returns false, all behavior falls back to current direct-terminal mode
  • tmux session dies while detached: Health check (5s) detects dead sessions and removes the agent
  • Multiple VS Code windows: Only adopts tmux sessions whose JSONL file belongs to the current workspace
  • Extension dispose/deactivate: Does NOT kill tmux sessions -- they intentionally persist
  • Backward compatibility: PersistedAgent.tmuxSessionName is optional; old persisted data works unchanged

Testing

  1. tmux path: Launch agent -> verify tmux ls shows session -> close terminal -> character goes semi-transparent -> click to reattach -> close via X button -> tmux session killed
  2. No tmux: Rename tmux binary -> launch agent -> verify direct-terminal behavior unchanged
  3. Persistence: Launch agent -> close VS Code -> reopen -> agent restored as detached -> click to reattach
  4. Auto-discovery: Launch agent -> close VS Code -> reopen -> orphaned tmux session found and shown
  5. Health check: Detach agent -> manually tmux kill-session -> wait 5s -> agent removed

Source: pixel-agents-hq/pixel-agents