No description
  • Rust 83.8%
  • JavaScript 8.2%
  • CSS 6.7%
  • HTML 1.3%
Find a file
Ingvi Gautsson c0d7af58ce Start restored, branched and reopened Claude from a real shell
These windows ran `sh -c 'claude ...; exec $SHELL'`. That sh has no job
control, so Ctrl-Z stopped Claude with no shell to run `fg` in, and the
window was stuck. Now the window starts the user's shell and types the
claude command into it once it waits at its prompt, as restored devenv
shells already did: Ctrl-Z suspends Claude to that shell, `fg` brings
it back, and the command is in the shell's history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-14 10:04:06 +00:00
docs Show what plain terminals report: commands, progress, titles, compose, other agents 2026-11-06 23:51:36 +00:00
src Start restored, branched and reopened Claude from a real shell 2026-11-14 10:04:06 +00:00
web Web overview: ocean cards take their state's colour; no mark on the window shown 2026-11-09 13:27:48 +00:00
.gitignore Initial sirkus: multiplexer with sidebar, Claude hook tracking, scrollback 2026-11-05 15:03:38 +00:00
Cargo.lock Start restored, branched and reopened Claude from a real shell 2026-11-14 10:04:06 +00:00
Cargo.toml Start restored, branched and reopened Claude from a real shell 2026-11-14 10:04:06 +00:00
LICENSE Add the MIT licence; remove DESIGN.md 2026-11-06 22:18:16 +00:00
README.md Start restored, branched and reopened Claude from a real shell 2026-11-14 10:04:06 +00:00

sirkus

A terminal multiplexer in the spirit of GNU Screen, built for running many Claude Code sessions at once. Your windows are listed in a sidebar on the left, and each Claude session shows its title, its state, and its project directory and git branch:

sirkus with five windows: Claude sessions that are done, working, needing input and idle, and a plain shell. The selected session is asking for permission to run a command.

  • ● red: needs input (a permission prompt or a question). Dismissing it with Esc makes the session idle again.
  • ⠹ yellow: working. That includes a session whose turn is over while commands it started in the background still run: it is waiting for their results. Once they've ended, Claude normally picks the results up, and the session finishes when that turn does. If Claude hasn't picked them up within 30 seconds, it counts as finished.
  • ● green: finished. A session that finished while you were on another window turns idle once you've been on its window for 2 seconds (flicking past keeps it marked). One that finishes in the window you're on stays marked until you type into it.
  • ○ dim: idle (the LCARS and ocean buttons leave idle sessions unlabelled)

Ctrl-a Tab jumps to the next session that needs you. Sessions that are blocked come first, then finished ones you haven't seen, oldest first. Plain shells, progress bars, docker compose and other coding agents show their state too (see Other programs).

Install

cargo install --path .
sirkus install-hooks      # adds sirkus hooks to ~/.claude/settings.json (a backup is kept)

The hooks report Claude's state to sirkus. Outside sirkus they do nothing. sirkus uninstall-hooks removes them again.

Use

sirkus                # start a session (named main, main2, …)
sirkus -S work        # start a session called "work"
sirkus -ls            # list sessions
sirkus -r [name]      # reattach (detaching other clients)
sirkus -x [name]      # attach alongside other clients
sirkus -R [name]      # reattach, or start one if none is running
sirkus web [name]     # use a session from a web browser (see below)

In a sirkus window, start claude as usual.

Several terminals can be attached to a session at once (sirkus -x). As in screen, each one has a view of its own: its own window, its own size, its own overview and prefix key. Each window has a size of its own too. A window that only one place shows takes that place's size. A window shown in several places keeps its size: it is cut off where it doesn't fit and leaves room where it's smaller. Ctrl-a F fits the window to the terminal you press it in. The windows, the sidebar's width, the theme and the draft belong to the session, so every terminal shares them.

Keys

All keys start with the prefix Ctrl-a:

Windows are numbered by their place in the list, so numbers change as windows come and go. A new window (c) goes at the end, a branch (B) right below the window it branches, and the windows after it move down one.

Key Action
Tab jump to next Claude session needing input
c new window
n / p next / previous window
1–9, 0 select window (0 = window 10)
Ctrl-a previous window
< / > move the window up / down the list
A rename window
k kill window (in a split, the terminal with the keyboard)
d detach
o / e split the terminal: a shell below / beside it
arrows the terminal that way in a split window; otherwise previous / next window
w write text to paste into the window (an editor)
O / " overview of all windows (fullscreen)
b toggle sidebar
B branch the Claude session into a new window
r reopen a recently closed Claude session
T reopen the last closed Claude session
N toggle desktop notifications
t next theme (saved as the default)
F fit the window to this terminal
[ scroll mode
a send a literal Ctrl-a
? help
\ quit (Enter or y confirms)

Mouse

  • The wheel scrolls back through history, like in Claude Code. Typing jumps back to the bottom. Programs that use the mouse themselves, Claude Code included, get the wheel events.
  • Click a sidebar entry to switch to that window. Drag an entry up or down to move the window there in the list; the windows are renumbered to match.
  • Double-click the empty part of the sidebar, below the entries, to open a new window.
  • Drag the divider between the sidebar and the terminal to resize the sidebar. The width is saved in the config file.
  • Drag to select text; releasing the button copies it to both the clipboard and the primary selection. Double-click selects a word, triple-click a line. Hold Shift to select inside programs that capture the mouse.
  • Middle-click pastes the primary selection (whatever was last selected, here or in another program) into the terminal under the pointer. Programs that capture the mouse get the click instead; Claude Code pastes the primary selection itself.
  • Copies go out both as OSC 52, which works over ssh, and through wl-copy (Wayland) or xclip/xsel (X11) when the server runs on a desktop. That covers terminals that ignore OSC 52, such as GNOME's. Without those tools, middle-click pastes the last text copied in sirkus.

Branching a Claude session

Ctrl-a B branches the Claude session in the current window, like Claude Code's /branch: a new window opens in the session's project directory and runs claude --resume <id> --fork-session, which continues a copy of the conversation under a new session ID. The branch opens right below the window it came from. The original session carries on untouched. When the branched Claude exits, the window drops to a shell. The branch includes the conversation as Claude Code has saved it, so wait for a turn to finish if you want all of it.

Reopening the last Claude session

Ctrl-a T resumes the Claude session closed last with claude --resume, in a new window in its project directory, whether Claude exited, its window was killed, or /clear replaced it. A session whose window was killed goes back to that window's place in the list, and gets its name back. Press it again for the one closed before that. With no session closed since sirkus started, it takes the newest one from the recent list (below).

Recently closed Claude sessions

Ctrl-a r lists the Claude sessions that were closed, most recent first, with the title, directory, branch and when each was closed. A session counts as closed when Claude exits, its window is killed, sirkus quits, or /clear replaces it with a new one. Sessions that never got a prompt aren't listed. Choose one with the arrow keys (or j/k) and press Enter to resume it with claude --resume in a new window, in its project directory; x removes it from the list and Esc closes the menu. The list is kept in ~/.local/state/sirkus/recent.jsonl and shared by all sirkus sessions; a session that runs again drops off it.

Split windows

Ctrl-a o splits the terminal with the keyboard, opening a shell below it in the same directory; Ctrl-a e opens one beside it. Each press splits again, so a window can hold any number of terminals, stacked and side by side. A new terminal beside takes half the room of the one it split; a new one below takes 40% of the window's height, at most 24 rows (and at most half the terminal it split). The new terminal gets the keyboard. Drag a dividing line to move it. Ctrl-a and an arrow moves the keyboard to the terminal that way, as does a click; the lines around the terminal with the keyboard are brighter, with arrows pointing at it. In a window that isn't split, Ctrl-a and an arrow switches windows as before.

All the terminals belong to the same window and its sidebar entry. When you start claude in one of the others, it becomes a window of its own, with its own entry, and the rest share its room. When a shell exits, its neighbours get its room; when the window's own terminal exits, the next one takes the window over. Restoring a session brings its splits back, with their sizes and the terminal that had the keyboard.

Writing text

Ctrl-a w opens an editor over most of the screen, for writing a longer text, such as a prompt of several paragraphs, before sending it. Its buttons:

  • Submit (or Alt-Enter) pastes the text into the window, or into the terminal with the keyboard in a split, and clears it. It goes in as a paste, without pressing Enter, so Claude Code takes all the lines as one message; press Enter in the window to send it.
  • Submit & keep pastes it the same way, but keeps the text for next time.
  • Clear empties it.
  • Close (or Esc, or Ctrl-a w again) closes the editor and keeps the text.

Tab moves between the text and the buttons. In the text, the arrows, Home/End (with Ctrl: the start and end of the text), PgUp/PgDn, Ctrl or Alt with the arrows (a word at a time), Backspace/Delete, Ctrl-W (delete a word), Ctrl-U and Ctrl-K (delete to the start or end of the line) and pasting work, and clicking places the cursor. The session has one text, whichever window you send it to; the web page edits the same text, and quitting with Ctrl-a \ keeps it for sirkus -S.

Overview

Ctrl-a O replaces the terminal with a fullscreen list of all windows. Each window gets a large state badge, its directory, git branch and Claude session, and what it is doing (the current tool, the first prompt, or for plain shells the last line on screen). Keys:

  • ↑/↓ or j/k: move the selection
  • Enter or a number: open a window
  • < / >: move the selected window up / down the list
  • Tab: select the next session needing input
  • B: branch the selected window's Claude session
  • x: kill the selected window
  • q/Esc: go back to the terminal

Clicking a card also opens it.

Themes

Add -t <name> (or --theme <name>) when starting or attaching to change the session's look, for example sirkus -t lcars or sirkus -r work -t matrix. The session keeps its theme until an attach picks another; theme = "..." in the config sets the default. Ctrl-a t cycles through the themes in a running session and saves the one you land on as the default. All themes except classic need a terminal with 24-bit colour.

Theme Look
classic the default
lcars Star Trek LCARS: elbow-shaped bars and a column of coloured buttons, one per window, in orange and pink
ocean the LCARS layout in blues and steel, with titles as written and without window numbers (they show while Ctrl-a waits for its key), roomier buttons when there is space, the directory line in a darker shade and a cyan bar marking the active window (lcars2 still works)
qbasic the QBasic editor: a menu bar, blue windows, a scroll bar and a status bar
c64 a Commodore 64 screen: light blue border, start-up banner and READY.
matrix green phosphor monochrome, with digital rain in the empty part of the sidebar that falls faster the more your terminals print
amber an amber monochrome CRT
synthwave neon pink and cyan on deep purple, with a sunset over a grid in the empty part of the sidebar; the grid rolls toward you while Claude works
gameboy four shades of green on a light screen, the window list in a pixel box with a ▶ on the active window
ocean matrix
The ocean theme: each window is a large blue, teal, orange or red button with its state written on it, and the selected one is marked by a cyan bar. The matrix theme: green on black, with katakana and digits raining down the empty part of the sidebar.
synthwave lcars
The synthwave theme: neon pink and cyan on deep purple, with a striped sun setting over a pink grid below the window list. The LCARS theme: upper-case window buttons in blue, gold, red and violet, numbered 01 to 05, framed by orange elbow bars.

The themes also recolour what runs in the windows, so Claude Code fits the look: its 24-bit colours (the orange, diff backgrounds, syntax highlighting) and other programs' 256 colours take the theme's own hues at about the same brightness: the ocean blues, teal and coral, the LCARS oranges, golds and blues, the synthwave neons. In c64 and qbasic they snap to the machine's 16 colours (with QBasic backgrounds from the 8 dark ones), keeping a red or green diff background red or green. Text is kept readable against its background. classic leaves colours alone; the monochrome themes and gameboy reduce everything to their own shades anyway.

In the LCARS themes, a button's colour and label show the window's state. In qbasic, click a menu entry (New, Overview, Notify, Sidebar, Help) to use it, and click the scroll bar's arrows or track to scroll. In c64, READY. turns into an error when sessions need input.

Notifications

Ctrl-a N turns desktop notifications off and on. The choice is saved in the config file and applies to sessions started later.

When a session that isn't on screen needs input or finishes, sirkus sends a desktop notification (notify-send, falling back to OSC 777) and rings the bell. So does a shell command that ran for 10 seconds or more and ended off screen.

Other programs

Titles

Programs that set the terminal title (vim and neovim with set title, ssh sessions, htop, …) show it in the sidebar while they run. The shell's own title is left out: plain shells keep their name.

Shell commands

With shell integration, the shell tells sirkus when a command starts and ends (OSC 133). Add this to the end of your shell's startup file (after a prompt framework like starship, which sets up the prompt itself):

eval "$(sirkus shell-init bash)"            # ~/.bashrc
eval "$(sirkus shell-init zsh)"             # ~/.zshrc
sirkus shell-init fish | source             # ~/.config/fish/config.fish

Outside sirkus it prints nothing. Fish 4 reports commands without it.

The sidebar then shows:

  • ⠹ cyan: a command runs and prints (▸ while it's quiet, or a full-screen program)
  • ✔ green / ✘ red: the command ended, or failed (the overview and the LCARS buttons show its exit code), while you weren't looking. Like a finished Claude session, the mark goes once you've been on the window for 2 seconds, or type into it.

A command that ran for 10 seconds or more notifies when it ends off screen, and Ctrl-a Tab visits failed commands, then successful ones, after the Claude sessions.

Progress

Programs that report progress with OSC 9;4 (as cargo, winget, and some build tools do) get a progress bar in place of the directory, with the percentage.

docker compose

While docker compose up (or watch) runs in the foreground of a window, sirkus asks docker about its services every few seconds and lists them in the sidebar, coloured by state: green running, yellow starting or not healthy yet, red exited with an error or unhealthy, grey stopped. The window is named after the project's directory, and its mark shows the worst of them (▲). The same options (-f, -p, --profile, …) are used to ask, so it finds the right project.

Other coding agents

sirkus notify reports another agent's state from that agent's hooks, the way sirkus install-hooks sets it up for Claude Code. The window then shows the agent like a Claude session (needing input, working, done, idle), notifies, and is on Ctrl-a Tab's list, until the agent exits.

sirkus notify --state input|working|done|idle|end [--agent NAME] [--message TEXT]

--agent names the agent (the program's name otherwise); --message says what it's doing or asking. done is the default. Outside sirkus it does nothing.

Codex CLI (~/.codex/config.toml) calls notify when a turn is complete; sirkus reads the event it passes and shows the last message:

notify = ["sirkus", "notify", "--agent", "codex"]

Gemini CLI hooks (~/.gemini/settings.json):

{
  "hooks": {
    "BeforeAgent": [{ "hooks": [{ "name": "sirkus", "type": "command", "command": "sirkus notify --agent gemini --state working" }] }],
    "AfterAgent": [{ "hooks": [{ "name": "sirkus", "type": "command", "command": "sirkus notify --agent gemini --state done" }] }],
    "Notification": [{ "hooks": [{ "name": "sirkus", "type": "command", "command": "sirkus notify --agent gemini --state input" }] }]
  }
}

Aider runs a command when it's waiting for you:

aider --notifications --notifications-command "sirkus notify --agent aider --state done"

Other agents with hooks or a notification command (OpenCode plugins, GitHub Copilot CLI and Cursor's agent hooks) work the same way: call sirkus notify with the state that fits each event.

Web

sirkus web                    # the running session, or a new one if none runs
sirkus web work               # session "work", started if it isn't running
sirkus web work --port 8080   # another port (the default is 7380)

sirkus web prints an address such as http://127.0.0.1:7380/?token=…; open it in a browser. The page has the window list on the left, with the same states as the terminal sidebar, and the window's terminal on the right. Terminal clients and any number of browser tabs can use the session at once. Each browser tab picks its own window, like each terminal does, and windows are sized the same way (see Use): Ctrl-a F fits a window to the terminal or tab you press it in. Browser tabs in the background don't count. Finished marks clear in a browser tab as they do in the terminal. sirkus web serves until you press Ctrl-C or the session ends; the session keeps running when it stops.

The page takes the prefix keys c, n/p, 1–9, </>, Tab, k, A, w, o, e, the arrows, O, B, b, t (the next colours in the menu below) and F, and Ctrl-a a sends a literal Ctrl-a. It splits windows the same way, with draggable dividers. The window's other actions (writing text, splitting below or beside, rename, branch, kill, a new window, the overview) have buttons too. The editor (w, or Write) takes Ctrl-Enter as well as Alt-Enter to submit. Copy with Ctrl-Shift-C (or Ctrl-C while text is selected) and paste with Ctrl-Shift-V. Choose the colours at the bottom of the window list: light or dark (following the system by default), or one of the sirkus themes, whose colours the programs in the windows then take, as they do in the terminal. The synthwave page goes further than the terminal: neon glow, a sun setting over a grid that rolls faster while Claude works, and a glowing screen. Drag the edge of the window list to resize it (double-click the edge for the default width); each browser keeps its own width. Drag an entry in the window list to move the window up or down the list; on a touch screen, hold the entry for a moment, then drag. The overview shows each window as a card with its full title (up to three lines), its directory, branch, program and session; the Terminals switch at the top right (or t) adds each window's last lines, and the browser remembers it. The address names the view (#window=3, or #overview), so reloading the page keeps it, and you can bookmark a window. On a phone the window list slides in from ☰, and a row of keys (Esc, Tab, Ctrl, the prefix and the arrows) sits below the terminal.

Security. By default the server listens on 127.0.0.1 only, so only this computer can reach it, and the page needs the random token in the address, which changes each time sirkus web starts. Anyone with the address can type into your terminals. To use the session from another device, prefer an SSH tunnel (ssh -L 7380:127.0.0.1:7380 host, then open the address on the device). --bind 0.0.0.0 listens on every network interface instead; the connection is not encrypted, so only do that on a network you trust.

Configuration

~/.config/sirkus/config.toml (all keys optional). sirkus updates notifications and sidebar_width itself when you change them with Ctrl-a N or by dragging the sidebar, leaving the rest of the file, comments included, as it is.

prefix = "C-a"          # prefix key
sidebar_width = 32
scrollback = 10000      # lines per window
shell = "/bin/zsh"      # default: $SHELL
notifications = true    # desktop notifications at startup (toggle with Ctrl-a N)
theme = "classic"       # or lcars, ocean, qbasic, c64, matrix, amber, synthwave, gameboy
bell = true

Persistence

sirkus records each session's windows in ~/.local/state/sirkus/. Quitting with Ctrl-a \ keeps that record, and so does the server dying (a reboot, for example). Starting the session again with sirkus -S <name> (or sirkus -r <name>) offers to restore its windows in their directories, and Claude sessions are resumed with claude --resume, with the options Claude was started with (--model, --permission-mode and so on; not the prompt, or which session to resume). A Claude session that never got a prompt has no conversation to resume, so a new one starts there instead, with the same options. A session that had finished without you seeing it comes back marked done (●). sirkus starts your shell and types the claude command into it, as you would, so Ctrl-Z suspends Claude to that shell and fg brings it back; the same goes for branched and reopened sessions. Split windows come back split, each terminal in its directory, and the window that was showing shows again, with the same terminal in it focused. sirkus -ls lists the sessions that can be restored. When every window in a session exits on its own, the record is removed.

Quitting with Ctrl-a \ also saves what each shell had on its screen and in its scrollback, colours included, and a restored shell starts below it. A full-screen program's screen (an editor, less) isn't saved, and Claude windows get their conversation back from claude --resume instead. If the server dies without quitting, shells come back empty.

A window that was in a devenv shell starts devenv shell again, in the directory it was started in and with the options it had. Once devenv's shell is up, sirkus types into it what ran there: going back to the directory the window was in, and claude --resume for a Claude session that ran in devenv. Leaving devenv's shell leaves you in your own, as before. A shell started with --no-reload doesn't show its other options, so it comes back with the project's defaults.

To move running sessions to a new version of sirkus: install it, quit each session with Ctrl-a \, then start it again with sirkus -S <name>.

License

MIT. See LICENSE.