Skip to content
ExpectedArgumentPublic

About

A home for your Claude Code sessions: every session in its own tmux (Linux) or psmux (Windows) window, live state from Claude Code hooks (working · needs you · your turn), Remote Control status, and automatic claude --resume after reboots and power cuts. Pure bash / PowerShell, no daemon.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

22 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

roost

A home for your Claude Code sessions. roost runs every Claude Code session in its own window of a terminal multiplexer, reads each one's live state from Claude Code's hook events, and brings all of them back, resumed where they stopped, after a reboot, a power cut or a dropped SSH / RDP connection.

Install · How it works · Commands · FAQ · For AI agents

License: MIT Platforms Written in Multiplexer Daemon

The roost dashboard on Windows: six sessions in six states, with Remote Control, model, effort and advisor per session, and the status bar listing only the sessions that need you

roost is an independent community project. It is not made by or affiliated with Anthropic.

Why

Agent sessions are long-lived, but the terminal that hosts them is not. Close a tab, lose Wi-Fi or reboot for an update, and the context of a dozen sessions is scattered across transcripts you have to find and resume by hand. And while they run, the one that is blocked on a permission prompt looks exactly like the ones that are working.

roost fixes both: sessions live in tmux (Linux) or psmux (Windows), a registry fed by Claude Code hooks knows the state and id of every session, and one command (or the boot itself) puts them all back.

Highlights

  • Know which session needs you. Every window carries its state, from ● working to ? blocked on a decision to * finished and waiting for your next message. The status bar lists only the sessions that need you, and the terminal tab says 🐦 roost ?1 *2, or 🐦 roost@host ?1 *2 when you came in over SSH. Ctrl+B a jumps to the next one: decisions first, then the ones waiting for you.
  • Keep groups of sessions apart in workspaces. Map folders to workspaces in one small file, and each workspace gets a multiplexer session of its own: its own dashboard, status bar, Ctrl+B a and tab title (🐦 roost:api ?1). The dashboard's top line lists every workspace with what waits for you there, and the status bar names the other workspaces that want you. Tab, a click or Ctrl+B ( / ) switch; roost move NAME WS puts a session elsewhere.
  • Survive closed terminals, dropped connections and reboots. Close the terminal or drop SSH and the sessions keep running. After a reboot or a power cut, roost reopens each one with claude --resume <id>. Linux does it from systemd before anyone logs in, Windows at sign-in. Sessions interrupted mid-task get a short "continue where you stopped" prompt; idle ones just reopen.
  • Restore exactly what you had. The permission mode and the claude.ai connector setting of each session come back with it. bypassPermissions is never restored automatically.
  • See what each session is running. A Remote Control column shows which sessions you can reach from another device. MODEL, EFFORT and ADVISOR columns show what each session last ran with, including mid-session /model, /effort and /advisor changes.
  • Stay out of the way. roost never passes --model or effort flags and never edits your Claude Code settings beyond one hooks section, written once, with a backup. The hooks print nothing, always exit 0, and do nothing in sessions roost did not start.
  • Run on plain shell. bash and PowerShell, tmux and psmux, jq on Linux. No daemon, no service of its own on Windows, nothing downloaded at install time.
Linux Windows
Sessions survive closing the terminal, SSH / RDP drops ✓ ✓
Live state per session from Claude Code hooks ✓ ✓
Status bar with only the sessions that need you, tab title with counts ✓ ✓
Jump to the next waiting session (roost next, Ctrl+B a) ✓ ✓
Workspaces: a multiplexer session, dashboard and status bar per group of sessions ✓ ✓
Resume after reboot / power loss with claude --resume ✓ before login (systemd) ✓ at sign-in
Continue prompt only for interrupted sessions, crash-loop guard ✓ ✓
Permission mode and connectors restored ✓ ✓
Remote Control column ✓ ✓
Model, effort and advisor columns ✓ ✓
Dashboard watch-style list, click to jump interactive: click, arrows, keys, wheel
Detects a claude that exited and leaves its window as a plain shell ✓ SHELL ✓ × exited
Self-test, health check, uninstaller ✓ ✓

How it works

flowchart LR
    claude(["claude<br/>one per window"]) -- "hook event + JSON" --> hook["roost-hook<br/>silent · always exit 0"]
    hook -- "write, then rename" --> reg[("registry<br/>sessions/NAME.json")]
    hook -- "state glyph" --> win["tmux / psmux<br/>window"]
    reg --> ui["dashboard<br/>status bar · tab title"]
    cc[("Claude Code files<br/>sessions/PID.json<br/>transcripts")] -.->|read-only| ui
    reg --> restore["roost restore"]
    restore -- "claude --resume ID" --> claude
Loading

Hooks. Claude Code calls roost-hook on SessionStart, UserPromptSubmit, Stop, Notification, PostToolUse and SessionEnd. The hook writes one small JSON file per session (name, directory, session id, transcript, state, permission mode, connectors) by write-then-rename, so a reader never sees half a file, and it puts the state glyph on the window. PostToolUse fires after every tool call, so on Windows it runs asynchronously behind a 30 ms check and starts PowerShell only while the session shows ?.

stateDiagram-v2
    direction LR
    state "quiet ○" as quiet
    state "working ●" as working
    state "needs you ?" as ask
    state "your turn *" as turn
    state "parked ◌" as parked
    [*] --> quiet: SessionStart
    quiet --> working: UserPromptSubmit
    working --> ask: permission prompt or question
    ask --> working: answered (PostToolUse)
    working --> turn: Stop
    turn --> working: UserPromptSubmit
    working --> parked: reboot, power loss, window closed
    parked --> working: roost restore, was busy (+ continue prompt)
    parked --> quiet: roost restore, was idle
    turn --> [*]: /exit
Loading

Two state values. state (busy or idle) decides whether a restored session gets the continue prompt. The display state (working, needs you, your turn, quiet) drives the glyphs. A permission prompt changes only the display, so a session that was working when the power went out still counts as interrupted.

Liveness without guesswork. A session counts as open only while claude really runs in its window. Linux asks tmux what the pane runs. Windows matches the window's process id and its start time, so a reused PID is never mistaken for a running session. Only /exit or /logout forgets a session; everything else leaves it parked for restore.

Restore pipeline (roost restore --boot on Linux, the Startup entry on Windows):

  1. Wait until api.anthropic.com answers, so sessions do not resume into a dead network.
  2. Crash-loop guard: after three boots within 30 minutes, sessions are resumed without the continue prompt, so a machine that keeps crashing does not keep re-running the task that crashed it.
  3. Reopen every parked or exited session with claude --resume <id>, its permission mode and connectors, staggered a few seconds apart.
  4. Append the continue prompt only for sessions whose registry state is busy.

What roost reads from Claude Code, always read-only and failing soft (an empty cell, never an error):

  • Hook payloads: session id, transcript path, permission mode, notification type.
  • ~/.claude/sessions/<pid>.json: Remote Control. bridgeSessionId is set while a session is connected; pid and process start time are matched, and a session handed to a background job follows the job's process.
  • The end of each transcript: message.model, effort and advisorModel of the last main-thread assistant entry, read from the last 64 KB (more when an entry is larger). Hooks carry no advisor and the model only on some starts; the transcript has all three.

Quick start

Linux

Needs tmux 3.2+, jq and claude. Tested on Ubuntu 24.04 with tmux 3.4.

git clone https://github.com/ExpectedArgument/roost && cd roost/linux
./install.sh              # command + hooks + tmux config
./install.sh --systemd    # also: start at boot and resume after power loss, before login (sudo; takes over at the next boot if tmux already runs)
roost new api ~/src/api   # first session (add -c to continue the last chat in that directory)
roost                     # dashboard;  Ctrl-b a  next waiting;  Ctrl-b w  all sessions;  Ctrl-b d  detach
roost doctor ; roost selftest

Remote: ssh -t HOST roost. Optional hard-stall recovery (hardware watchdog, panic reboot, NVMe power-state workaround) lives in linux/extras/recovery; it is hardware specific and off by default.

Windows

Needs PowerShell 7, claude and psmux (winget install psmux --version 3.4.0; verify the hash on the release page). Best in Windows Terminal. The Windows port is newer than the Linux one; please report problems.

git clone https://github.com/ExpectedArgument/roost; cd roost
pwsh -File windows\install.ps1     # scripts, hooks, sign-in restore, `roost` command, Windows Terminal profile
roost selftest ; roost doctor
roost new api C:\src\api
roost                              # dashboard: click, arrows + Enter or a session's key;  Ctrl+B a  next waiting;  Ctrl+B d  detach

install.ps1 also adds a roost profile with the 🐦 icon to the Windows Terminal new-tab menu. Sessions reopen when you sign in (Startup folder, no admin rights). Windows has no boot service without a sign-in unless you enable auto sign-in yourself.

Commands

Command
roost open the dashboard (attach to the multiplexer session)
roost new NAME [DIR] [claude args] start a session; extra args go to claude (-c, --model sonnet, …). DIR defaults to $ROOST_PROJECT_ROOT/NAME when it exists. --connectors keeps claude.ai connectors on, --ws WS puts it in workspace WS
roost NAME switch to a session (reopens it first if it is parked or exited), in whichever workspace it is
roost ls [WS] list sessions with state, age, RC, model, effort and advisor, under a heading per workspace (or one workspace's)
roost restore [NAME] [--ws WS] reopen parked and exited sessions, each in its workspace
roost next jump to the next session of this workspace that waits for you (? first, then *); bound to Ctrl+B a
roost ws [WS] list the workspaces, or open the screen of one; Ctrl+B ( / ) switch to the previous / next
roost move NAME [WS] / --all put a session in workspace WS (or, without WS, where its folder says); an open one is reopened there with --resume, never while it works or waits for a decision
roost close NAME /exit the session cleanly, close its window, forget it
roost forget NAME drop the registry entry only
roost adopt NAME Linux: register a tmux window where claude already runs
roost doctor health check: tools, hooks, restore at boot / sign-in
roost selftest test the scripts against a throw-away setup, touching no real session

Configuration

Linux Windows
default claude flags ~/.config/roost/claude-flags (--permission-mode auto) %LOCALAPPDATA%\roost\config\claude-flags
continue prompt ~/.config/roost/resume-prompt.txt …\config\resume-prompt.txt
sessions that keep claude.ai connectors (or *) ~/.config/roost/connectors-allow …\config\connectors-allow
workspaces: WORKSPACE FOLDER per line ~/.config/roost/workspaces …\config\workspaces
state and registry ~/.local/state/roost %LOCALAPPDATA%\roost

Sessions not listed in connectors-allow start with ENABLE_CLAUDEAI_MCP_SERVERS=false, which saves tokens when you do not need them. The default is *, so nothing changes until you edit it.

Workspaces

# ~/.config/roost/workspaces  (Windows: %LOCALAPPDATA%\roost\config\workspaces)
work      ~/src/work
personal  ~/src/personal

A session belongs to the workspace whose folder holds its directory (the longest folder wins); every other session is in default. roost new NAME DIR --ws WS and roost move NAME WS override that for one session. Each workspace is a multiplexer session of its own, roost for default and roost-WS for the others, so Ctrl+B a, the status bar and the tab title only count its sessions, and the dashboard lists only them under a line of all workspaces. A session moved while it runs is closed the way a reboot would close it and resumed in its new workspace; one that works or waits for a decision is not moved. With the file empty everything stays in default and nothing changes. After editing the file, roost move --all reopens open sessions in the workspace they now belong to.

Environment: ROOST_PROJECT_ROOT, ROOST_AUTOCONTINUE=0 (never send the continue prompt), ROOST_CLAUDE_BIN, ROOST_STAGGER, ROOST_NET_WAIT, and on Windows ROOST_MOUSE=0 (the dashboard leaves the mouse alone).

Guarantees

  • Your settings stay yours. The installers add one hooks section to ~/.claude/settings.json after backing it up, detect existing entries and leave the file untouched when nothing is missing. model, effortLevel and everything else are never written.
  • Hooks cannot hurt a session. They print nothing (hook output would reach the model's context), always exit 0, never block on anything and ignore sessions roost did not start.
  • Nothing is assumed about Claude Code internals. Its per-process files and transcripts are undocumented, so every read is defensive: a missing file or field means an empty cell, never a failed command.
  • No surprises with permissions. A restored session gets back its own permission mode, except bypassPermissions, which is never restored automatically. roost close sends Esc before /exit, so a pending permission prompt is cancelled, never answered.
  • No telemetry, no daemon. The only network access is the reachability check before a boot-time restore.

Testing

linux/bin/roost selftest                         # own tmux socket, state dir and a fake claude: new, hooks, ls, title, next, restore, SHELL, close, RC, workspaces
pwsh -File windows\roost.ps1 selftest            # temp home, dry-run psmux, roost-run.ps1 with a fake claude: hooks, restore, run, next, transcripts, RC, workspaces
pwsh -File windows\probe-statusbar.ps1 -Click …  # renders the psmux status bar through a pseudoconsole and clicks its labels

Tested on Ubuntu 24.04 with tmux 3.4, and on Windows 11 with PowerShell 7.6, psmux 3.4.0 and Windows Terminal 1.24.

FAQ

What happens when the machine reboots or loses power? Every registered session is parked. On Linux with --systemd, the tmux server and the restore unit start at boot, before anyone logs in. On Windows, restore runs when you sign in. Each session comes back with claude --resume, in its own directory, with its permission mode. Those that were working get the continue prompt.

Does roost change my Claude Code configuration? Only the hooks section, once, with a backup. Model, effort, advisor, permissions and every other setting are left alone. Extra flags you give roost new are passed to claude unchanged.

The dashboard is crowded. Can I split it? Yes: put the folders of each group in the workspaces file (see Workspaces). Every workspace gets its own dashboard, status bar and Ctrl+B a; the top line of each dashboard still shows what waits for you elsewhere.

A session shows × exited or SHELL. What happened? claude quit, for example after a crash or a Ctrl+C, and its window stayed open as a plain shell. roost restore NAME resumes it in a fresh window, and so do picking it on the dashboard and roost NAME.

Does it work remotely? Yes. Detach, disconnect, come back: ssh -t HOST roost on Linux. On Windows, sessions survive a dropped Remote Desktop connection. With Claude Code's Remote Control, the RC column shows which sessions you can reach from another device.

Why psmux on Windows? It is a native tmux-compatible multiplexer for Windows, so sessions run in real Windows processes, with no WSL. roost works around a few psmux 3.4.0 quirks, documented in windows/CLAUDE.md.

Pasted text loses letters on Windows (for example every i on a Turkish Q keyboard). That is psmux 3.4.0 bug #884: a pasted letter that the layout types with AltGr arrives as a Tab. It is fixed in psmux after 3.4.0. Until then, roost attaches with PSMUX_VT_INPUT=1 when your keyboard layout is affected, which avoids it. roost doctor tells you when that applies.

How do I remove it? linux/uninstall.sh, or pwsh -File windows\uninstall.ps1 [-DryRun]. Both back up settings.json and remove only roost's hook entries. Your conversations are never touched.

For AI agents

Read CLAUDE.md first (shared design: hook events, state values, registry, invariants), then linux/CLAUDE.md or windows/CLAUDE.md for the port you change. Mirror a behaviour change in both ports unless it is platform specific. Never test against the user's live multiplexer session: both self-tests and the recipes in the port notes use throw-away sessions, homes and a fake claude.

License

MIT

About

A home for your Claude Code sessions: every session in its own tmux (Linux) or psmux (Windows) window, live state from Claude Code hooks (working · needs you · your turn), Remote Control status, and automatic claude --resume after reboots and power cuts. Pure bash / PowerShell, no daemon.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages