A Claude Code plugin that lets an agent work in the user's own tmux panes — the same SSH session, the same REPL, the same build — instead of a sandboxed shell nobody else can see.
claude-tmux-bridge is a Claude Code plugin plus a Rust CLI, tb, that drives tmux panes the user has pointed it at: running shell commands, sending text to a REPL or TUI, reading the screen back, waiting on a command or a screen state, and handling secret prompts through a companion daemon, tb-vault, so a password never has to pass through the agent's own transcript.
An agent that only has its own sandboxed shell can't SSH into the box the user is already logged into, can't type into the REPL the user is already debugging in, and can't watch a long build the user is already tailing. tb gives it exactly those panes — the ones the user already has open — instead of a parallel, invisible copy of the environment.
The easy-to-miss benefit is what this does for safety, not just capability. Handing an LLM raw tmux access would let it reach any pane on the user's machine and act on it without a prior look. tb is built the other way around: every pane is unreachable until the user points the agent at it, every write is refused until the agent has actually read the pane first, and reaching outside the agent's own tmux window requires the user to read a confirmation code off their own screen and hand it back — nothing the agent can do unlocks that by itself.
The domain overloads a few words. This table pins each one to a single meaning used consistently below.
| Term | Meaning |
|---|---|
| Pane | A tmux pane, identified internally by its stable %N id (PaneId), never by the session:window.pane coordinate, which shifts as windows are renumbered. |
| Mark | Registering a pane under a name (tb mark <name> --pane <id>) — the act that makes a pane writable and returns its current screen. |
| View | A saved screen snapshot tied to one pane, used to detect whether the pane changed since it was last looked at. |
| Handle | The identifier tb start returns for a running async command, later passed to tb poll/tb await/tb cancel. |
| Scope | Which panes are reachable at all — by default, only the ones sharing the agent's own tmux window ($TMUX_PANE). |
| Grant | A user-confirmed, time- or use-bounded widening of scope, or of the raw-tmux block — requested by the agent, only ever confirmed by the user. |
| Agent pane | A pane that resolves to another live Claude Code session in the session registry — routed to via SendMessage, never driven or read directly. |
| Vault | tb-vault, the separate daemon that holds captured secrets in memory and pipes them into a pane's prompt without they ever entering the agent's own context. |
tb never talks to tmux for a pane the agent hasn't been pointed at. Marking a pane records a view of its screen; every write re-checks that view before acting, and every reachability check first passes through the scope boundary and the agent-pane guard.
flowchart TD
AGENT["Claude Code agent"]
TB["tb CLI"]
SCOPE{{"in scope?\n(own tmux window,\nor a user grant)"}}
LOOK{{"pane read\nand unchanged?"}}
PEER{{"resolves to a live\nClaude Code session?"}}
PANE[("tmux pane\n(shell / ssh / repl / tui)")]
SENDMSG["SendMessage\nto the peer session"]
VAULT[("tb-vault daemon\nin-memory secret store")]
USER["user, at the same terminal"]
AGENT --> TB
TB --> SCOPE
SCOPE -->|no| REFUSE1(["refused: outside window scope\n-> ask the user for tb grant"])
SCOPE -->|yes| PEER
PEER -->|yes, kind=agent| SENDMSG
PEER -->|no| LOOK
LOOK -->|no| REFUSE2(["refused: pane not read,\nor changed since read"])
LOOK -->|yes| PANE
TB -->|vault store/capture/send| VAULT
VAULT -.->|piped secret, never returned to the agent| PANE
USER <-->|shares, sees, types into| PANE
USER -->|confirms grant codeword| SCOPE
Two enforcement points do the real work: the scope check (which panes exist at all, from this agent's point of view) and the look-before-you-touch check (whether a write is allowed to go out at all). Both are checked in the CLI itself, not left to agent judgment.
Use it when the agent needs to act inside a terminal the user is also using: an SSH session to a box that needs a password typed by hand, a Python/Node REPL mid-debug, a build or log tail the user wants to watch land in real time, or handing a stuck TUI dialog back to the user without losing the pane. It's a shared workspace, not a sandbox — the user sees everything the agent does in it, because it's the same screen.
It's not the right tool for work the agent can do in its own isolated shell with no need for the user's session, credentials, or screen — that's more overhead than benefit. It's also not a way to reach panes the user hasn't agreed to share: scope defaults to the agent's own tmux window, and widening it is a user action, not a flag.
- Claude Code, to install and run the plugin.
- tmux, already running, with the user attached to a session that shares a window with the agent's own pane.
- macOS (Apple Silicon or Intel) or Linux (x86_64 or arm64). No Windows build.
- Building from source additionally requires the Rust toolchain (this repo pins
rust = "1.95"inmise.toml).
claude plugin marketplace add navistau/claude-marketplaceThen install claude-tmux-bridge from that marketplace. The tb and tb-vault binaries are downloaded automatically the next time Claude Code starts.
git clone https://github.com/navistau/claude-tmux-bridge.git
cd claude-tmux-bridge
mise install # pins rust 1.95, see mise.toml
cd src/cli
cargo build --releaseThis produces target/release/tb and target/release/tb-vault. To make the plugin's hooks use this build instead of downloading a release binary, place both where plugin/hooks/ensure-binary.sh looks first — ${CLAUDE_PLUGIN_DATA}/bin/ (the plugin data directory Claude Code assigns this plugin) — and write the matching version into ${CLAUDE_PLUGIN_DATA}/.binary-version (the value of version in plugin/.claude-plugin/plugin.json); with both in place, ensure-binary.sh skips its download.
tb-vault is a separate long-running daemon, not spawned automatically — start it once per host (e.g. tb-vault &, or under your own init system) before using tb vault subcommands. tb status reports whether it's reachable as vault_running.
The full command surface, its refusal rules, and the workflows built on it (agent panes, secrets, scope and grants) live in plugin/skills/tmux-bridge/SKILL.md — that file is what the agent itself reads at runtime, so it's the authoritative reference, not a copy kept in sync by hand here. The commands most often reached for:
| Command | What it does |
|---|---|
tb list |
CSV of reachable panes: name, stable_id, kind, cwd. |
tb mark <name> --pane <id> |
Register a pane under a name; returns its current screen. |
tb status <name> |
Pane state: kind, host, cwd, ssh_target, vault_running, and (for an agent pane) the peer to message. |
tb run <name> "<cmd>" |
Execute a shell command; blocks; returns exit_code + output diff. |
tb send <name> "<text>" |
Send text to a REPL/TUI/prompt, then verify it submitted. |
tb watch <name> --until <regex> / --idle <ms> |
Block until the pane's screen reaches a state, instead of polling. |
tb vault store/capture/send/list/purge |
Secret handling via the tb-vault daemon. |
tb grant --status |
Show what scope/raw-tmux access is currently granted. |
| Message | Source | Meaning |
|---|---|---|
you have not read pane 'x' |
send/run/start/submit/signal |
No view of the pane has ever been recorded — tb read x (or tb mark) first. |
pane 'x' has changed since you read it |
same commands | The screen moved since the last recorded view; the delta is included in the message. |
pane 'x' is still changing |
same commands | The pane is mid-draw or a command is running — tb watch/tb await instead of retrying immediately. |
pane 'x' is outside your window scope |
any pane-targeted command | The pane isn't in the agent's own tmux window and no grant covers it — ask the user for tb grant scope <window|session|server>. |
refusal naming a peer |
send/read/run/start/watch/submit on an agent pane |
The pane resolves to another live Claude Code session — use SendMessage to the named peer instead. |
status=awaiting-confirmation |
tb grant … |
A confirmation code was shown on the user's own screen; ask them to read it back, then run tb grant --confirm <CODE>. |
ensure-binary: <what failed> — tmux-bridge binaries unavailable this session (will retry next session). |
SessionStart hook, stderr |
The binary install/update failed (offline, download error, a mismatched attestation, ...); session start still proceeds, and the next session retries. |
- Every command refuses with a scope error. Confirm the agent's
$TMUX_PANEis set (it must be running inside tmux) and that the target pane shares that window. If it's a different window on purpose, ask the user to runtb grant scope session --for <duration>and read back the confirmation code. - A write refuses even right after
tb mark.markonly satisfies the check for the pane it just marked. A different pane, or a colleague agent's read of the same pane, doesn't count — read the specific pane directly. tb vaultcommands fail to connect.tb-vaultisn't running. Start it (tb-vault &, or under your init system) and re-check withtb status'svault_runningfield.SendMessageto a worker pane seems to go nowhere. A receiving session running inbypassPermissionsmode holds inbound cross-session messages for approval by default. SetcrossSessionInbound: acceptin the worker's own settings for unattended driver-to-worker messaging to land.- A pane that should be
agentshows as something else. Agent-pane detection depends on a live session-registry entry. A session mid-turn, or on an older Claude Code version, has none and won't be detected. Absence of theagentkind is not proof the pane is safe to drive directly. - Raw
tmuxcommands get blocked. That'sblock-raw-tmux.shdoing its job — ask the user fortb grant raw-tmuxrather than looking for a wrapper form that slips past it.
Scope and the look-before-you-touch check are enforced in the CLI, not left as documented conventions an agent could ignore — a pane outside scope, or unread, is refused by tb itself regardless of what the agent's own reasoning concludes. Neither check can be satisfied by the agent alone: scope only widens via a grant the user confirms on their own screen, using a code tb never shows the agent.
The agent-pane guard is best-effort by design: it only fires on a pane that currently resolves to a live entry in Claude Code's own session registry, so a session mid-turn, or one predating cross-session messaging, isn't detected. This is a known, accepted gap rather than a bug — the guard exists to catch the common case (screen-scraping a peer that could instead be messaged), not to prove a pane's safety in general.
--force is scoped narrowly on purpose: it lets a caller read an unregistered pane by raw %N and bypass the agent-pane refusal on send/read/run/start/watch/submit, but it does nothing to the scope boundary and does not let a caller write to an unregistered pane. Only a user-issued tb mark does that.
Secrets never round-trip through the agent. tb vault capture prompts the user directly in the pane; tb vault send pipes a stored value straight into a prompt. The agent can trigger both, but never sees the value in either direction — anything captured by screen-reading is redacted before it reaches the agent's transcript, on a best-effort heuristic-plus-deterministic basis, not a guarantee.
src/cli's integration suite (crates/tb/tests/integration/) is the authoritative suite: it builds the real tb/tb-vault binaries and drives them against real tmux panes and, for the tmux-assumptions probes and most agent/vault scenarios, Docker containers running a range of shells and TUIs — the actual environments this tool has to work correctly against, not a mock of them. As of this release it's 332 unit tests plus 133 integration tests (cargo test -p tb --test integration -- --list).
test_run_against_animated_tui_completes is a known timing flake (a 5-second stabilization budget against an animated TUI, occasionally exceeded on a loaded machine) — see CONTRIBUTING.md.
The shell layer (tmux-exec, block-raw-tmux.sh) has its own suite, run separately:
cd src/cli && cargo test
shellspecSee CONTRIBUTING.md for the branch model, build/test commands, and the documentation style used in this README.
See SECURITY.md to report a vulnerability.
AGPL-3.0-only — see LICENSE.