{"slug": "terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts", "title": "Terminal Sessions – VSCode extension for tmux terminals that survive restarts", "summary": "Terminal Sessions, a new VSCode extension, wraps every terminal in a tmux session that survives full editor restarts, crashes, and reboots, with first-class awareness of AI coding agents including Claude Code, Codex, Antigravity (agy), and Grok (xAI). The extension shows live agent state, context usage, cost, and past conversations in the sidebar, and supports macOS, Linux, Remote-SSH, and Remote-WSL, but not native Windows without WSL or SSH.", "body_md": "Persistent terminal sessions for Cursor and VS Code, with first-class awareness of your AI coding agents — **Claude Code, Codex, Antigravity ( agy), and Grok (xAI)**. Terminals survive full editor restart, organized per workspace, and the sidebar shows live agent state: working/tool/waiting, context usage, cost, last user and assistant messages. Browse, read, name, resume, and clean up every past conversation on your machine, across all four agents.\n\nEvery terminal is wrapped in a tmux session whose server runs independent of the editor. Quit Cursor, reboot the window, crash the renderer: Claude Code, dev servers, REPLs, migrations, SSH sessions keep running. Reopen the workspace and everything is where you left it.\n\n| Platform | tmux backend | Notifications | Click-to-focus on notif | Status |\n|---|---|---|---|---|\nmacOS (local) |\nNative | macOS Notification Center + modal alert | Yes (osascript / terminal-notifier) | Full support |\nLinux (local) |\nNative | `notify-send` (libnotify) + optional `zenity` modal |\nYes (zenity) | Full support |\nRemote-SSH / Remote-WSL |\nNative (tmux on the remote) | IPC-forwarded VS Code toast or modal in the local Cursor window | Yes (via VS Code API) | Full support |\nWindows (native) |\nNot supported — needs WSL or Remote-SSH | Falls back to VS Code toast | No | Requires WSL or SSH |\n\nWindows users:tmux does not run natively on Windows. The extension works normally on:\n\nWSL— install the extension in the WSL-Remote window, install tmux in WSL (`sudo apt install tmux`\n\n)Remote-SSH— connect to a Linux/macOS host, install the extension on the remote sideNative Windows (PowerShell, cmd, Git Bash) is not supported.\n\nRemote-SSH / Remote-WSL users:you do NOT need to install`terminal-notifier`\n\nor`libnotify`\n\non the remote machine. The extension detects the remote extension host via`vscode.env.remoteName`\n\nand routes notifications through the VS Code API, which forwards them to your local Cursor UI automatically. The`Show terminal`\n\nbutton on waiting alerts still works across the IPC bridge.\n\nVS Code's built-in `terminal.integrated.persistentSessionReviveProcess`\n\nonly survives window reloads, not full app quits. Child processes always die when the editor fully restarts. This extension solves it by wrapping each terminal in a tmux session whose server daemon is independent of the editor process.\n\nThree moving pieces, each independent, composed to give you a persistent and observable terminal layer.\n\n**1. tmux as the process keeper.** Every persistent terminal you open is actually `tmux attach-session`\n\nagainst a named session on a tmux server that runs outside of Cursor. Quit Cursor, reboot the window, crash the renderer: the shells and anything they spawned (Claude Code, `npm run dev`\n\n, a migration, a long SSH) keep running in the tmux server. When you reopen the workspace, the extension offers to re-attach. Sessions are named `ts-<workspace-hash>-<tabId>`\n\n, so two projects or two git worktrees of the same repo never collide.\n\n**2. A managed ~/.terminal-sessions/tmux.conf.** The extension writes its own tmux config with defaults tuned for Cursor: mouse on, large scrollback, OSC 52 clipboard, modern CSI-u keys, DECSET 2026 synchronized output, and since v0.11 the\n\n`CLAUDE_CODE_NO_FLICKER`\n\nand `CLAUDE_CODE_DISABLE_MOUSE_CLICKS`\n\nenv vars baked in so Claude Code renders cleanly in alt-screen and trackpad scroll stays inside the conversation view. Your own `~/.tmux.conf`\n\nis never touched; the managed file loads it at the end if it exists, so your personal theme or keybindings still apply.**3. AI-agent awareness via hooks + transcript tailing.** If you opt in, the extension installs lifecycle hooks for each agent you use — `SessionStart | UserPromptSubmit | PreToolUse | PostToolUse | Notification | Stop | SessionEnd`\n\nfor Claude (`~/.claude/settings.json`\n\n), the equivalent events for Codex (`~/.codex/hooks.json`\n\n) and Antigravity (`~/.gemini/antigravity-cli/settings.json`\n\n, plus a `statusLine`\n\nfor live context). A single shared forwarder normalizes each agent's differing event payload and writes a JSON line to `~/.terminal-sessions/agent-events.log`\n\n, tagged with the agent id and the tmux session the agent is running in. A file watcher feeds those events into an in-memory map and sets the per-session state (working, tool, waiting, idle). In parallel, a transcript tailer follows the agent's on-disk conversation file and, through that agent's provider, extracts model, token counts, context-window usage, and cost. The sidebar reads both streams and renders the merged snapshot. A conversation can only belong to one tmux session at a time; starting the same conversation in a different tab transfers ownership so the sidebar never shows duplicate live states. Everything below works the same way for Claude, Codex, and Antigravity — Claude is simply the most battle-tested provider.\n\n**What persists across what**\n\n| Event | tmux session | Shell process | Claude process | Conversation (`.jsonl` ) |\n|---|---|---|---|---|\n| Close a tab | kept | kept | kept | kept |\nReload window (`Cmd+R` ) |\nkept | kept | kept | kept |\nQuit Cursor (`Cmd+Q` ) |\nkept | kept | kept | kept |\n| Restart Session command | killed | killed | killed | kept (auto-resumed in v0.11+) |\n| Mac reboot | killed | killed | killed | kept (recreate from index) |\n\n**Persistent sessions** across full editor quit/restart via tmux**Workspace-scoped naming**— each project gets its own namespace (8-char path hash), no cross-project collisions** Git-worktree aware**— different paths = different hashes, so worktrees automatically get separate sessions** Auto-resume toast**on workspace open — \"Found N sessions from last time — [Resume All / Pick... / Ignore]\"** Configurable auto-restore**—`auto`\n\nresumes all,`ask`\n\nprompts,`off`\n\ndisables**Max age filter**— skip auto-restore for sessions older than N hours (default 72h)** Safe tab close**— closing a terminal tab detaches; session keeps running in the background** Explicit kill**via command palette, right-click on sidebar item, or \"Kill all for this workspace\"** Auto-prune**stale sessions after configurable days (default 14)** Lock a session against Kill**— right-click →** Lock (Protect from Kill)**; a padlock takes the Kill button's place and the session can no longer be killed — not from the row, not by \"Kill all for this workspace\", not by auto-prune — until you right-click →**Unlock (Allow Kill)**. The inline padlock is a deliberate indicator only (clicking it won't unlock), so an important long-runner survives an accidental click. Restart and Stop stay available\n\n**Terminal Profile \"Persistent Session\"**— available in the`+ ∨`\n\ndropdown; can be set as default so every new terminal auto-wraps in tmux**Sidebar tree view**— grouped by workspace, with status indicators (attached vs detached) and relative timestamps. Lives** under the Explorer**by default; drag it (or right-click its header →*Move View*) to your own Activity Bar icon, the panel, or the secondary sidebar**Rich hover tooltip**— hovering a session row shows its tmux ID, workspace, the actual** start folder (cwd)**for subfolder sessions, created/last-attached times, and — while an agent is active — the live** Conversation ID**, model, API-equivalent cost, context %, token totals, and the last user/assistant messages** Sidebar badge**(v0.11+) — a red numeric badge appears on the view's host icon (the Explorer, or wherever you've placed the view) when Claude sessions need attention.`waiting`\n\ncount takes priority (user permission pending), falls back to`working`\n\ncount. Tooltip explains which is which**Click terminal tab → reveal sidebar session**(v0.11+) — clicking any`Terminal Sessions #N`\n\ntab in the VS Code terminal panel selects and highlights the matching row in the Terminal Sessions sidebar, auto-expanding its workspace group**Status bar badge**—`⚡ ts: 2▶ 4⇄`\n\n(attached · detached), click to open the attach picker**View conversation**— open the session's agent transcript as readable Markdown; reads the transcript`.jsonl`\n\ndirectly, so it works on stopped sessions too (not just live ones) and shows the real conversation regardless of renderer**Rename sessions** with custom labels (persisted in index)**Custom icon & color per session**— pick from codicons (robot, rocket, flame, database, server, bug, etc.) and ANSI colors; applied to the terminal tab icon and sidebar**Restart session**(v0.11+: with Claude auto-resume) — kill the current tmux session (any program in it, incl. Claude Code) and respawn a fresh shell; keeps the label, icon, color, and workspace. If Claude was running, the extension auto-detects its session ID, verifies the transcript is still on disk, and runs`claude --resume <id>`\n\nin the new shell. Conversation context survives, Ink renderer state is clean**Smart click behavior**— clicking a session that's already attached focuses its existing terminal tab instead of opening a duplicate** Right-click context menu**on sidebar items — View Conversation, Restart, Stop/Start, Rename, Icon, Color, Mute notifications, Lock/Unlock, Kill** Explorer right-click → \"Open in Integrated Terminal - Persistent\"**— on any folder, opens a persistent tmux session rooted at that folder. The VS Code tab description reflects the actual folder name (v0.11+; earlier versions always showed the workspace root)\n\n**Custom**— drag sessions in the sidebar to rearrange; order persisted across restarts (stored per-session in`~/.terminal-sessions/index.json`\n\n)**Recently used**— most recently focused session floats to top** Creation order**— oldest first (default, backward-compatible)** Alphabetical**— by session label- Toggle via the\n`$(list-ordered)`\n\nicon in the sidebar title bar; dragging automatically switches to Custom\n\n**Filter modes**— show`all`\n\n, only`running`\n\n, or only`stopped`\n\nsessions via the`$(filter)`\n\nicon in the title bar (`terminalSessions.sidebarFilterMode`\n\n)**Stop / Start a session**— pause a session (kills the tmux session but keeps the row, with its label, icon, color, and agent history) and start it again later, instead of killing it outright. Stopped rows get an inline`$(play)`\n\n**Groups & master groups**— organize sessions under named groups, and groups under master groups, via right-click`New Group…`\n\n/`New Master Group…`\n\n/`Move to Group…`\n\n, with drag-and-drop and per-group rename/delete. Purely organizational; the underlying tmux sessions are untouched**Re-attach All Ghost Terminals**— one click (`$(debug-restart)`\n\nin the title bar) revives the orange/disconnected tabs you get after a Cursor restart, in sidebar order**Reveal Session Folder**— right-click → open the session's working directory in the OS file manager (Finder/Explorer), or jump to its row from the terminal tab via`Reveal in Terminal Sessions View`\n\n**Copy Last Conversation ID / Path**— right-click → put the most recent agent conversation's UUID, or the full path to its transcript`.jsonl`\n\n, on the clipboard (works for Claude, Codex, and Antigravity)\n\n— one collapsible row groups every subagent a Claude session spawned, so sessions with lots of agents stay tidy. Auto-expanded while anything is live; collapsed when everything finishes. Tooltip previews the first five agents with their state`🤖 Agents (N running · M done)`\n\nfolder per session**Live per-subagent rows**— state icon (spinner / tools / check), elapsed time, current tool with input preview, last streamed message. Nests recursively for agents that spawn sub-subagents**Inline subagent counter in the session description**—`Terminal Sessions waiting input · 69% ctx · 🤖 2 running`\n\n(falls back to`🤖 N done`\n\nafter completion). See live agent activity without expanding**Agent label**—`<subagent_type> — <description>`\n\npulled from the`Agent`\n\n/`Task`\n\ntool input (e.g.`researcher — MCP servers for note apps`\n\n). Tooltip shows depth, parent agent id, and timestamps**Background-agent support**— Claude Code ≥ 2.1.119 spawns subagents via the`Agent`\n\ntool with`run_in_background: true`\n\n; their activity is written to per-agent transcripts in`<main-jsonl>/subagents/agent-<id>.jsonl`\n\n(not as sidechain messages). The tailer scans that sibling directory on a 3-second poll, so live state surfaces within ~3 s even without the main jsonl being written. The classic synchronous`Task`\n\ntool path keeps working too(default`terminalSessions.showCompletedSubagents`\n\n`true`\n\n) — keeps completed agents visible so short runs don't flicker in and out. Flip to`false`\n\n(or run`Terminal Sessions: Toggle Show Completed Subagents`\n\n) to focus only on live work— right-click a subagent row → opens its transcript jsonl in an editor tab jumped to the first line where that agent was registered. For background agents this is the small per-agent file, much easier to read than the main conversation transcript`Open Subagent Transcript`\n\ncommand**Auto-done on parent idle**— when the parent session has been idle for 2+ minutes, stragglers flagged`working`\n\nare marked done in the rendered snapshot so the sidebar doesn't spin forever on interrupted agents\n\n**One sidebar, four agents**— the same live status, context %, cost, history, and auto-resume work for** Claude Code**,** Codex**,** Antigravity**(`agy`\n\n), and**Grok**(xAI). Each tracked session shows which agent it's running, so a row reads`Codex working 12s`\n\nvs`Claude working 12s`\n\n**Auto-detection**— Claude is always on; Codex, Antigravity and Grok turn on automatically when their CLI (`codex`\n\n/`agy`\n\n/`grok`\n\n) is found on your`PATH`\n\n. Override explicitly with`terminalSessions.enabledAgents`\n\n(e.g.`[\"claude\", \"codex\"]`\n\n)**Per-agent hooks, one forwarder**—`Terminal Sessions: Install AI Agent Hooks`\n\nwrites the right hook into each agent's own settings file (`~/.claude/settings.json`\n\n,`~/.codex/hooks.json`\n\n,`~/.gemini/antigravity-cli/settings.json`\n\n); a single shared script normalizes their differing event payloads. Your model and MCP config are never disturbed**Grok needs no hooks**— Grok's lifecycle hooks are project-scoped and trust-gated, so the extension tracks it without installing anything: it discovers live Grok sessions from`~/.grok/active_sessions.json`\n\n, matches each to its tmux pane by process tree, and tails the session's ACP`updates.jsonl`\n\nfor status, tokens, and messages. Nothing is written into your projects**Agent-correct resume**— restart/resume runs the right command per agent:`claude --resume <id>`\n\n(cwd-sensitive),`codex resume <id>`\n\n(restores its own recorded cwd), or`agy --conversation <id>`\n\n. The extension picks it from the session's recorded agent automatically**Provider abstraction**— adding more agents later is a new provider file, no core changes; all three share one tracker, state machine, and notification path\n\n**Per-session state indicator**— icon + description reflect whether the agent is`working`\n\n, running a`tool`\n\n(with tool name),`waiting`\n\nfor user permission, or`idle`\n\n(with time-since). State is derived from the transcript directly so it stays correct even when hooks are out of date**API-equivalent cost in USD**— real cost per session computed from the transcript using the live Anthropic rate card, per-model (Opus 4.7 at $5/$25 in/out, Opus 4.1 at $15/$75, Sonnet at $3/$15, Haiku at $1/$5, plus separate cache-read and 5-min/1-hour cache-write tiers). Retried turns are de-duplicated by`message.id`\n\n; subagents on different models are counted automatically with their own rate. Sidebar shows`opus · $55.25 · 364 turns`\n\n; tooltip shows per-model breakdown and the raw token totals**Context-window gauge**(v0.11 fix) — the`31% ctx`\n\nsuffix appears next to every Claude-active session. Crosses`terminalSessions.contextWarnPct`\n\n(default 0.8) →`⚠ 87% ctx`\n\n. Limit is auto-detected per session: Opus/Sonnet 4.5+ default to 1M-context, older models to 200k; if any single turn exceeds 200k we pin the limit to 1M. Subagent turns are excluded because they have their own context**Nested detail rows**— under each active session, rows show last user message, last Claude reply, model/cost/turns, current tool with its input (e.g.`Bash: \"npm run build\"`\n\n); configurable`auto | always | off`\n\n**Search past sessions**—`$(search)`\n\nbutton in the sidebar (or`Terminal Sessions: Find Session by Prompt…`\n\ncommand) opens a fuzzy picker over every transcript on your machine. Jump to transcript, copy session ID, or reveal the cwd**Deduplicated live state**(v0.11+) — if you ran`claude --resume <id>`\n\nin multiple tabs over time, the tracker now transfers ownership on each new hook event, so only the tab currently running that conversation shows live state. Others snap back to idle\n\n**Resume Session from Archive**— the`$(history)`\n\nbutton on the sidebar toolbar (or`Terminal Sessions: Resume Session from Archive…`\n\n) opens a picker of**every** past conversation on disk, across all three agents, even when nothing is live in tmux for it. Defaults to the current workspace with a one-click toggle to show all projects. Accepting a row resumes it into your active session or a fresh persistent one**View Conversation**— right-click a session (or the eye button in the archive picker) to open a readable Markdown rendering of the conversation in VS Code's preview: user and assistant turns, with thinking blocks and tool calls in collapsible sections. No more squinting at raw`.jsonl`\n\n**Name Conversation**— give any session a friendly name; it shows in the archive picker and the viewer title. Names live in the extension's sidecar index, so the agent's transcript files are never modified**Clean Up Empty / Invalid Sessions**— a maintenance action (sidebar overflow`⋯`\n\nmenu) that finds empty or \"Invalid API key\" conversations and soft-deletes them into`~/.claude/projects/.bak`\n\n, with a preview and confirmation. The agent's own database and session index are never touched; moved files can be restored manually\n\n**Claude Stop notification**— fires when Claude finishes a response. Distinct from the waiting variant so you can glance at the sound/icon and know whether you need to act. Min-duration filter prevents notif-storms on short turns**Claude Waiting notification**(v0.11+) — fires when Claude blocks for user permission (tool approval, risky command, URL access). Distinct sound (default`Sosumi`\n\nvs`Glass`\n\nfor Stop),`⚠ Claude needs approval`\n\ntitle, subtitle is the session label. Two styles via`terminalSessions.waitingAlertStyle`\n\n:`banner`\n\n— standard macOS/Linux notification, auto-dismisses`alert`\n\n—**persistent modal dialog** with a`Show terminal`\n\nbutton that activates Cursor and focuses the matching tab. macOS uses`osascript display alert`\n\n; Linux uses`zenity --question`\n\n(if installed)\n\n**Click-to-focus**(v0.11+, macOS) — if`terminal-notifier`\n\nis installed (`brew install terminal-notifier`\n\n), clicking any notification banner or alert brings Cursor to the foreground instead of Script Editor. Without`terminal-notifier`\n\nnotifications still work, but click lands in Script Editor — see the Requirements section for the exact trade-off**Works over Remote-SSH / Remote-WSL**(v0.11+) — when the extension host runs on a remote machine (the tmux session lives on the server, Cursor runs on your laptop), OS native notifications posted from the remote can't reach your desktop. The extension auto-detects this via`vscode.env.remoteName`\n\nand routes through the VS Code API instead: waiting events become an IPC-forwarded warning toast (banner style) or a blocking modal dialog (`alert`\n\nstyle) that pops up in your local Cursor window. The`Show terminal`\n\nbutton still works the same way — click it and the extension iterates`vscode.window.terminals`\n\non the remote extension host and focuses the matching tab in your local UI. No extra setup on the remote; libnotify/terminal-notifier are not used in remote mode because they would be useless**Global on/off toggle**(v0.11+) — bell icon in the Terminal Sessions sidebar title bar toggles`notifyOnClaudeWaiting`\n\n. When off, the icon switches to`$(bell-slash)`\n\n. Command Palette also has`Terminal Sessions: Toggle Claude Waiting Alerts (Global)`\n\n**Per-session mute**(v0.11+) — right-click a session →`Mute Notifications`\n\n. Stop and Waiting notifications for that session are silenced until you unmute. Muted sessions display a`🔕`\n\nin the sidebar description. Useful for long-running experiments where you don't want beeps**Native macOS Notification Center**— mode-switchable (`auto`\n\n: native when Cursor is unfocused / toast when focused;`always`\n\n;`never`\n\n)**Native Linux**(v0.11+) — uses`notify-send`\n\nwith urgency`critical`\n\nfor warnings (sticky until dismissed on most desktop environments); falls back to VS Code toast if libnotify is missing**Sound picker**— 14 macOS built-in sounds (Glass, Ping, Hero, Pop, Sosumi, …). Separate settings for Stop (`notificationSound`\n\n) and Waiting (`notificationSoundWaiting`\n\n). Linux sound mapping is not implemented; sound is macOS-only**Long-running command alerts**— notification when a command takes longer than a configurable threshold (default 30s)** Post-reboot recovery**— \"Recreate Sessions from Index\" rebuilds your sessions after a reboot wiped the tmux server; optional`claude --resume <id>`\n\nhint toast so you can reattach Claude Code sessions by ID\n\nmacOS decides whether notifications show as auto-dismissing **banners** or sticky **alerts** at the OS level, not from the app. To make every notification stay on screen until you dismiss it:\n\n- Open\n**System Settings → Notifications** - Find\n**Script Editor**(that is the app that posts our notifications via osascript) - Change\n**Notification style** from`Banners`\n\nto`Alerts`\n\nAlerts get a `Show`\n\nbutton and stay in the top-right corner until you click it or Close. The `terminalSessions.waitingAlertStyle: \"alert\"`\n\nsetting is an alternative that works without changing System Settings — it produces a modal dialog instead of a banner.\n\nThe extension generates and manages `~/.terminal-sessions/tmux.conf`\n\nwith sensible defaults tuned for Cursor. Your default `~/.tmux.conf`\n\nis **not** touched — this file only loads when the extension starts a session.\n\nDefaults include:\n\n**Mouse on**, 50 000 line scrollback, 10ms escape time, focus events,`exit-empty off`\n\n**True color** support (`tmux-256color`\n\n+`xterm-256color:RGB`\n\n)**OSC 52 clipboard**(`set-clipboard on`\n\n) — selections copy to the system clipboard through Cursor automatically**DECSET 2026 synchronized output** passthrough (`terminal-features xterm*:sync`\n\n) — apps like Claude Code that emit the sequence stop producing flicker through tmux— TUIs can send OSC/kitty-graphics/clipboard sequences through unfiltered`allow-passthrough on`\n\n— modern CSI-u encoding so Ctrl+Shift combos reach Claude Code correctly`extended-keys on`\n\nand`CLAUDE_CODE_NO_FLICKER=1`\n\nbaked in via`CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1`\n\n`set-environment -g`\n\n(v0.11+) — every new tmux window inherits these, so Claude renders in alt-screen and trackpad scroll stays inside the conversation view. No shell rc edit required**Drag-select stays in copy-mode**(tmux default exits copy-mode and jumps to prompt — override uses`copy-selection-no-clear`\n\n)**Trackpad-friendly scroll**— 1 line per tick (default 5 is too fast on trackpads)** Custom prefix**(screen-style).`Ctrl+A`\n\n`Ctrl+A Ctrl+A`\n\nsends a literal Ctrl+A**Menu on**— copy mode, paste, splits, zoom, rename, kill, respawn, reload config`Ctrl+A q`\n\n**Status bar off**— Cursor has its own UI, saves a row** Inherit**`~/.tmux.conf`\n\nif present (append your theme/keybinds)\n\nOn updates, if the managed file is out of date you get a one-time toast offering to regenerate it. The old file is backed up next to it with a timestamp before any rewrite.\n\nTwo commands:\n\n`Terminal Sessions: Open tmux.conf`\n\n— opens the file in an editor`Terminal Sessions: Reload tmux Config`\n\n— applies changes to all running sessions\n\n**Remote-SSH support**— runs on whichever side hosts the workspace (remote when connected over SSH, local otherwise). Install on both, the right copy activates per window. One VSIX works both local and remote**No lock-in**— everything is plain tmux. If the extension breaks you can`tmux ls`\n\nand`tmux attach -t ts-xxx`\n\nfrom any system terminal**Index file** at`~/.terminal-sessions/index.json`\n\nmaps workspace hashes to readable paths and labels (for debugging or external tools)\n\n**tmux** on your`PATH`\n\n:`brew install tmux`\n\n(macOS) or`sudo apt install tmux`\n\n(Debian/Ubuntu) or`sudo dnf install tmux`\n\n(RHEL/Fedora)**Claude Code ≥ 2.1.110** if you want the`CLAUDE_CODE_NO_FLICKER`\n\nrendering fix to take effect (earlier versions had a regression that wiped scrollback)\n\nThe extension works out of the box, but the click-to-focus behavior on notifications depends on small platform helpers. Without them, you still get the notification — you just can't click it to jump straight to the right terminal tab.\n\n**macOS**\n\n`brew install terminal-notifier`\n\n— makes**click on a** instead of bouncing you to Script Editor. Without it, notifications still show up, but they are posted via`Claude done`\n\n/`Claude needs approval`\n\nbanner focus Cursor`osascript`\n\nwhich attributes them to Script Editor.app; clicking \"Show\" opens Script Editor, not the IDE. With it, the extension uses`terminal-notifier -activate <Cursor bundle id>`\n\nso clicks land in Cursor.- For fully persistent banners that stay on screen until you dismiss them: System Settings → Notifications → Script Editor (or Terminal Notifier, if you installed it) → set Notification style to\n**Alerts** instead of Banners. Alternatively keep banners and flip`terminalSessions.waitingAlertStyle`\n\nto`\"alert\"`\n\nso waiting events come through as a modal dialog (persistent and click-to-focus, macOS-only).\n\n**Linux**\n\n`libnotify`\n\n— required for any native notification at all:`sudo apt install libnotify-bin`\n\n(Debian/Ubuntu) or`sudo dnf install libnotify`\n\n(RHEL/Fedora). Without it, the extension falls back to VS Code toasts (in-editor popups, auto-dismissing).`zenity`\n\n(optional) — enables the persistent modal dialog for waiting alerts (`waitingAlertStyle: \"alert\"`\n\n). Without it, the`alert`\n\nstyle silently falls back to a sticky`notify-send -u critical`\n\nbanner. Install with`sudo apt install zenity`\n\nor`sudo dnf install zenity`\n\n.\n\n**Node.js 20+**\n\n| Platform | Package | Without it | With it |\n|---|---|---|---|\n| macOS | `terminal-notifier` |\nNotifications appear but clicking them opens Script Editor | Notifications appear and clicks focus Cursor |\n| macOS | Notification style = Alerts (System Settings) | Banners auto-dismiss in 5s | Banners stay until dismissed, have `Show` button |\n| Linux | `libnotify-bin` |\nNotifications fall back to VS Code toasts | Native desktop notifications via `notify-send` |\n| Linux | `zenity` |\n`waitingAlertStyle: \"alert\"` falls back to sticky banner |\nReal modal dialog with `Show terminal` button |\n\n```\ncursor --install-extension terminal-sessions-0.20.14.vsix --force\n```\n\nOr in Cursor: Extensions panel → `⋯`\n\n→ **Install from VSIX...**\n\n```\ncd path/to/terminal-sessions\nnpm install\nnpm run compile\n# Press F5 in Cursor to launch an Extension Development Host\nnpm install\nnpm run package   # produces terminal-sessions-<ver>.vsix\n```\n\nThe extension runs on the workspace side (remote when connected over SSH, local otherwise). Install it on both so the right copy picks up per window.\n\n- Install tmux on the remote server\n- In a Remote-SSH window: Cmd+Shift+P → \"Extensions: Install from VSIX...\" → pick your local\n`.vsix`\n\n. Cursor uploads and installs on the remote automatically - Reload the remote window. Sidebar and commands operate against the remote tmux\n\n- In a WSL-Remote window, open a workspace under\n`\\\\wsl$\\...`\n\n- Install tmux in WSL:\n`sudo apt install tmux`\n\n- Install the extension in the WSL-Remote extension host (same VSIX flow as above)\n- Reload\n\n- Install tmux on the target machine\n- Install the extension and reload Cursor (full quit + reopen if you see stale state)\n- Run\n`Terminal Sessions: Set as Default Terminal Profile`\n\nso every`+`\n\nbutton creates a tmux-wrapped terminal - Find the\n**Terminal Sessions** section under the Explorer (or drag it out to its own Activity Bar icon / the panel — VS Code remembers where you put it) - Optional: run\n`Terminal Sessions: Install AI Agent Hooks`\n\nto enable live agent state + notifications for Claude, Codex, and Antigravity\n\n| Command | What it does |\n|---|---|\n`Terminal Sessions: New Persistent Terminal` |\nCreates a new tmux-wrapped terminal for the current workspace |\n`Terminal Sessions: Attach to Session...` |\nQuick-pick across all sessions (any workspace) |\n`Terminal Sessions: Resume All For Workspace` |\nOpen terminals for every detached session of the current project |\n`Terminal Sessions: Reveal Sidebar` |\nFocus the sidebar tree view |\n`Terminal Sessions: Kill Session` |\nPick a session to kill |\n`Terminal Sessions: Kill All Sessions for This Workspace` |\nClean up this project |\n`Terminal Sessions: Kill All Stale Sessions` |\nPrune sessions older than `pruneAfterDays` |\n`Terminal Sessions: Find Session by Prompt...` |\nFuzzy picker over every Claude transcript on your machine |\n`Terminal Sessions: Set as Default Terminal Profile` |\nWrite the VS Code setting so `+` auto-wraps |\n`Terminal Sessions: Open tmux.conf` |\nEdit `~/.terminal-sessions/tmux.conf` |\n`Terminal Sessions: Reload tmux Config` |\nApply config changes to running sessions |\n`Terminal Sessions: Install AI Agent Hooks` |\nWrites lifecycle hooks for each enabled agent into its own settings file (Claude `~/.claude/settings.json` , Codex `~/.codex/hooks.json` , Antigravity `~/.gemini/antigravity-cli/settings.json` ) |\n`Terminal Sessions: Uninstall AI Agent Hooks` |\nRemoves the hooks for all agents |\n`Terminal Sessions: Resume Session from Archive...` |\nPicker over every past conversation on disk (Claude/Codex/Antigravity); resume, view, or name any of them |\n`Terminal Sessions: Resume Other Session...` |\nResume a different past conversation into the current session (cross-agent) |\n`Terminal Sessions: Change Sidebar Filter` |\nShow all / running-only / stopped-only sessions |\n`Terminal Sessions: Fix Claude Code Rendering in Shell` |\nAppends `CLAUDE_CODE_NO_FLICKER=1` and `CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1` to your rc file (optional — v0.11 bakes these into tmux.conf so most users won't need this) |\n`Terminal Sessions: Toggle Claude Waiting Alerts (Global)` |\nFlip the `notifyOnClaudeWaiting` setting |\n`Terminal Sessions: Recreate Sessions from Index` |\nAfter a reboot, rebuild tmux sessions from the stored index |\nRight-click on sidebar session → `Restart` |\nKill + fresh shell; auto-resume the agent if detected |\nRight-click on sidebar session → `View Conversation` |\nRender the session's transcript as Markdown (reads the `.jsonl` directly — works on stopped sessions too) |\nRight-click on sidebar session → `Name Conversation...` |\nGive the session a friendly name (shown in the archive picker) |\nRight-click on sidebar session → `Stop` / `Start` |\nPause/respawn the tmux session while keeping the sidebar row |\nRight-click on sidebar session → `Copy Last Conversation ID` / `Copy Last Conversation Path` |\nClipboard the agent conversation's UUID or the full path to its transcript `.jsonl` |\nRight-click on sidebar session → `Reveal Session Folder` |\nOpen the session's working directory in Finder/Explorer |\nRight-click on workspace/group → `New Group…` / `Move to Group…` |\nOrganize sessions into (master) groups |\nSidebar overflow `⋯` → `Clean Up Empty / Invalid Sessions...` |\nSoft-delete empty/invalid conversations to `~/.claude/projects/.bak` |\nRight-click on sidebar session → `Rename` |\nSet a friendly label |\nRight-click on sidebar session → `Change Icon` / `Change Color` |\nPick custom icon or theme color |\nRight-click on sidebar session → `Mute Notifications` / `Unmute Notifications` |\nPer-session silencing |\nRight-click on sidebar session → `Lock (Protect from Kill)` / `Unlock (Allow Kill)` |\nProtect a session from Kill (padlock takes the Kill slot); Unlock to allow killing again |\nRight-click on sidebar session → `Kill` |\nTerminate that session (hidden while the session is locked) |\nRight-click on folder in Explorer → `Open in Integrated Terminal - Persistent` |\nNew tmux session rooted at that folder |\n\n| Key | Action |\n|---|---|\n`Ctrl+A q` |\nMenu: copy mode, paste, splits, zoom, rename, kill, respawn, reload config |\n`Ctrl+A Ctrl+A` |\nSend a literal `Ctrl+A` (for shell \"beginning of line\") |\n| Mouse drag-select | Copy to clipboard, stay in copy-mode |\n| Mouse wheel in pane | Enter copy-mode, scroll 1 line/tick |\n`q` or `Esc` or `Enter` |\nExit copy-mode |\n| Right-click | Cursor context menu (Copy/Paste/Kill Terminal/etc.) |\n\n| Setting | Default | Description |\n|---|---|---|\n`terminalSessions.tmuxPath` |\n`\"\"` |\nAbsolute path to tmux binary. Empty = autodetect from PATH and common locations |\n`terminalSessions.sessionPrefix` |\n`\"ts\"` |\nPrefix for session names, e.g. `ts-a3f2c71d-1` |\n`terminalSessions.enabledAgents` |\n`[]` (auto-detect) |\nWhich AI CLIs to track. Empty = Claude always on, Codex/Antigravity on when found on PATH. Override e.g. `[\"claude\",\"codex\"]` |\n`terminalSessions.autoRestore` |\n`\"ask\"` |\nOn workspace open: `auto` , `ask` , or `off` |\n`terminalSessions.autoRestoreMaxAgeHours` |\n`72` |\nSkip auto-restore for sessions older than this |\n`terminalSessions.pruneAfterDays` |\n`14` |\nOffer to prune sessions idle longer than this (`0` to disable) |\n`terminalSessions.sidebarSortMode` |\n`\"created\"` |\n`custom` , `mru` , `created` , or `alphabetical` |\n`terminalSessions.sidebarFilterMode` |\n`\"all\"` |\nFilter sidebar by state: `all` , `running` , or `stopped` |\n`terminalSessions.claudeSidebarDetails` |\n`\"auto\"` |\nExpand the nested rows under a Claude session: `auto` /`always` /`collapsed` /`off` |\n`terminalSessions.claudeNoFlicker` |\n`\"auto\"` |\n`CLAUDE_CODE_NO_FLICKER` mode. `auto` = off on Cursor (conversation copyable), on in VS Code (clean alt-screen). `on` = always clean, no copy from live view. `off` = always copyable, slight flicker |\n`terminalSessions.contextWarnPct` |\n`0.8` |\nThreshold (0-1) for the `⚠ 87% ctx` warning next to Claude state |\n`terminalSessions.nativeNotifications` |\n`\"auto\"` |\n`auto` (native when Cursor unfocused), `always` , `never` |\n`terminalSessions.notificationSound` |\n`\"Glass\"` |\nmacOS sound for Claude Stop notifications |\n`terminalSessions.notificationSoundWaiting` |\n`\"Sosumi\"` |\nmacOS sound for Claude Waiting notifications (distinct from Stop) |\n`terminalSessions.notifyOnClaudeStop` |\n`true` |\nSend a notification when Claude finishes a response |\n`terminalSessions.notifyOnClaudeWaiting` |\n`true` |\nSend a notification when Claude blocks for user permission |\n`terminalSessions.waitingAlertStyle` |\n`\"banner\"` |\n`banner` (auto-dismiss) or `alert` (persistent modal dialog with Show button) |\n`terminalSessions.claudeStopMinDurationSeconds` |\n`15` |\nSkip Stop notifications for turns shorter than this |\n`terminalSessions.autoResumeClaude` |\n`false` |\nAfter recreating sessions post-reboot, auto-run `claude --resume` |\n`terminalSessions.enableLongRunNotifications` |\n`true` |\nNotify when a command takes >N seconds |\n`terminalSessions.longRunThresholdSeconds` |\n`30` |\nThreshold for long-run notifications |\n\n```\n{prefix}-{8-char SHA-256 hash of workspace path}-{tab number}\n```\n\nExample: `ts-a3f2c71d-1`\n\nis the first persistent terminal opened in whatever workspace hashes to `a3f2c71d`\n\n. The index at `~/.terminal-sessions/index.json`\n\nmaps hashes to human-readable paths and labels.\n\nGit worktrees automatically get separate namespaces (different absolute paths → different hashes).\n\nPlain tmux under the hood:\n\n```\ntmux ls                         # list all sessions on the default socket\ntmux attach -t ts-a3f2c71d-1    # attach from any system terminal\ntmux kill-session -t ts-...     # kill from CLI if extension won't\n```\n\nClaude Code's TUI writes full-frame redraws into the main terminal buffer on every state change, which tmux faithfully captures, producing scrambled scrollback, duplicate prompts, and corruption after detach/reattach or during heavy subagent use. This is a Claude-Code-side issue (Ink/React renderer), not tmux. See `anthropics/claude-code#29937`\n\n, `#41814`\n\n, `#46981`\n\n.\n\nThe managed `~/.terminal-sessions/tmux.conf`\n\nnow emits\n\n```\nset-environment -g CLAUDE_CODE_NO_FLICKER 1\nset-environment -g CLAUDE_CODE_DISABLE_MOUSE_CLICKS 1\n```\n\nso every new tmux window inherits these at startup. No shell rc edit needed. The extension auto-prompts to regenerate older configs on upgrade.\n\n— Claude Code renders into the alternate screen buffer (like`CLAUDE_CODE_NO_FLICKER=1`\n\n`vim`\n\n,`less`\n\n,`htop`\n\n). tmux no longer captures each intermediate frame, so scrollback stays clean across detach/reattach and parallel subagents— clicks are handed to tmux (so you can still click-select panes, tabs, the sidebar, etc.) but scroll events still reach Claude Code. Trackpad scrolls Claude's conversation view directly.`CLAUDE_CODE_DISABLE_MOUSE_CLICKS=1`\n\n`DISABLE_MOUSE=1`\n\nalone would block trackpad scroll\n\nRequires Claude Code ≥ 2.1.110 (earlier versions had a regression that wiped scrollback).\n\n**Default is editor-aware ( terminalSessions.claudeNoFlicker: auto):** on\n\n**Cursor**, NO_FLICKER is\n\n**off** by default — Claude uses the classic renderer so the conversation lands in tmux scrollback and copies cleanly via pbcopy/xclip (Cursor mis-decodes Claude's own OSC 52 copy, so this is the only way to copy accented text). On\n\n**VS Code** it stays\n\n**on**(clean alt-screen; VS Code copies OSC 52 fine, so nothing is lost). Force it either way with\n\n`on`\n\n(always clean, but no copy from the live Claude view) or `off`\n\n(always copyable, at the cost of a little flicker on heavy redraws). Changing it regenerates and reloads tmux.conf; restart the Claude session to apply.If you started Claude in a session before the v3 config was applied, the env vars aren't in that shell yet. Either run `Restart Session`\n\non the sidebar (auto-resumes the conversation) or `exec bash`\n\n/ `exec zsh`\n\ninside the pane to pick them up.\n\n**From plain shell output (git, build logs, bash):** drag-select with the trackpad as usual. Selection copies to the system clipboard via OSC 52**From the Claude conversation:** press`Ctrl+O`\n\nthen`[`\n\ninside Claude. That dumps the current conversation view into the main tmux scrollback. From there, drag-select normally. Press`Ctrl+O`\n\nthen`/`\n\nfor Claude's own in-view search**Cmd+F / tmux copy-mode search** only sees content in the main buffer. The live Claude view lives in alt-screen, so it is not searchable that way — use`Ctrl+O`\n\n`/`\n\ninside Claude instead**Over Remote-SSH:** an agent's own copy (Claude/Codex \"copied to clipboard\") reaches your**local** machine's clipboard with correct UTF-8, accents included. A headless remote has no clipboard tool (`xclip`\n\n/`pbcopy`\n\nfail) and Cursor's terminal mis-decodes OSC 52 for non-ASCII, so the extension installs a tiny writer on the remote and bridges what it captures to the local clipboard through the VS Code API. No setup on the remote\n\nSee [CHANGELOG.md](/visul/terminal-sessions/blob/main/CHANGELOG.md).\n\nMIT", "url": "https://wpnews.pro/news/terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts", "canonical_source": "https://github.com/visul/terminal-sessions", "published_at": "2026-07-26 18:19:18+00:00", "updated_at": "2026-07-26 18:53:07.102342+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["Terminal Sessions", "VSCode", "Cursor", "Claude Code", "Codex", "Antigravity", "Grok", "xAI"], "alternates": {"html": "https://wpnews.pro/news/terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts", "markdown": "https://wpnews.pro/news/terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts.md", "text": "https://wpnews.pro/news/terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts.txt", "jsonld": "https://wpnews.pro/news/terminal-sessions-vscode-extension-for-tmux-terminals-that-survive-restarts.jsonld"}}