Skip to main content
Glama
devpark435

claude-session-bridge

by devpark435

English 한국어 中文 日本語

npm version license

claude-session-bridge

Let two (or more) Claude Code sessions talk to each other — and optionally orchestrate themselves.

You open one Claude Code session for your backend and another for your frontend. Normally they know nothing about each other — when the backend changes an API, you copy-paste the result into the frontend session by hand.

This tool removes the copy-paste. A session sends its result; sibling sessions receive it automatically. Turn on the optional auto mode and a finished backend can wake the frontend to react — hands-off ping-pong — while you keep full control of the safety rails (no auto-commits, deploy gates, loop guards) and change every setting from inside Claude.

New here? Read top to bottom — every command is included. You don't need to understand the internals to use it.


Table of contents


Related MCP server: neighbors

How to picture it

Two simple ideas:

  • Project = a chat room. Sessions launched with the same project name are in the same room and can talk. Different project name = different room, isolated.

  • Role = your nickname in that room. backend, frontend, infra — any label you like. You address messages to a role.

Session 1   project "shop"  role "backend"  ┐
                                            ├─ room "shop" (they talk)
Session 2   project "shop"  role "frontend" ┘

Session 3   project "blog"  role "backend"  ─── room "blog" (separate, isolated)

There is no "connect" button. Launching two sessions with the same project name is connecting them.


Requirements

  • Node.js 18+ — check with node --version

  • Claude Code (the claude command) — check with claude --version

  • tmux — optional. The default auto driver uses it to reach a role's live pane when one is open, but doesn't require it: without tmux, auto falls back to resuming or spawning a headless session instead. Only the tmux driver itself (forcing live-pane-only wakes) needs tmux installed.


Install (one time)

You do this once. Afterward you never edit these files again — the only thing that changes per session is two environment variables (explained next).

Recommended: install via npx, no clone needed. Run this inside a Claude Code session (so it targets that session's config dir — see the note below):

npx claude-session-bridge install    # registers the MCP server + hooks
session-bridge doctor                # verify what's set up, and where

install copies the package runtime to ~/.claude/bridge/runtime/<version>/ and registers that path in settings.json — not the npx cache, which npm prunes periodically and would silently break the MCP server. It also merges the receive hook, the block-git hook (blocks git commit/push/ reset), and a SessionStart/SessionEnd hook pair (records this session's live status, cwd, tmux pane, and session id — so the resume/auto drivers know what to wake later) into your active settings.json — idempotently, without touching your other settings. Add --no-block-git if you want sessions to commit on their own.

To remove everything install added:

session-bridge uninstall              # removes our MCP server + hook entries only
session-bridge uninstall --purge --yes  # also wipes the bus: messages, session registry, runtime copies

uninstall leaves every other setting untouched — it only strips entries it recognizes as its own.

Work accounts / multiple profiles. Claude Code reads $CLAUDE_CONFIG_DIR/settings.json when that env var is set (common for a separate work login), otherwise ~/.claude/settings.json. This is the #1 reason "I edited settings and nothing changed" — you edited the wrong file. Running session-bridge install inside a session of a given profile targets the correct file automatically. Run it once per profile. Check with session-bridge doctor, which prints the active config dir.

If you're working on this repo itself, install from your local checkout instead so settings.json points at your dist/, not a copied runtime:

git clone <this-repo-url> claude-session-bridge-mcp
cd claude-session-bridge-mcp
npm install
npm run build
npm link                          # optional: puts `session-bridge` on PATH
session-bridge install --no-copy  # registers this repo's dist/ directly
session-bridge doctor

--no-copy is also the flag install uses internally when it detects a dev checkout without a published version to copy from.

Add to the right settings.json (replace /ABSOLUTE/PATH):

{
  "mcpServers": {
    "session-bridge": {
      "command": "node",
      "args": ["/ABSOLUTE/PATH/claude-session-bridge-mcp/dist/server.js"]
    }
  },
  "hooks": {
    "UserPromptSubmit": [
      { "hooks": [ { "type": "command",
        "command": "node /ABSOLUTE/PATH/claude-session-bridge-mcp/dist/hooks/recv.js" } ] }
    ],
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [ { "type": "command",
        "command": "node /ABSOLUTE/PATH/claude-session-bridge-mcp/dist/hooks/block-git.js" } ] }
    ]
  }
}

All hooks no-op safely in non-bridged sessions, so they're fine installed globally. That's the whole setup.


Starting sessions with .bridge.json + up

Setting BRIDGE_PROJECT/BRIDGE_ROLE by hand (next section) works anywhere, but if your roles always live in the same repo paths, declare them once and let up do the typing. Drop a .bridge.json in your repo root:

{
  "project": "shop",
  "roles": {
    "backend": { "cwd": "." },
    "web": { "cwd": "../shop-web", "model": "sonnet" }
  }
}

cwd is resolved relative to the .bridge.json location (a role without it runs in that same directory); model and permissionMode are optional and seed the spawner's per-role config the first time up runs that role. Discovery walks up from your current directory like git does, so up works from anywhere inside the repo.

Launch one role:

session-bridge up backend          # spawns `claude` with BRIDGE_PROJECT/BRIDGE_ROLE set
session-bridge up backend --admin  # same, but grants BRIDGE_ADMIN=1 (can change spawner settings)

Launch every declared role at once:

session-bridge up

Inside tmux, this splits one new pane per role (your current pane is left alone) and tiles the layout — each pane's session auto-registers via the SessionStart hook. Outside tmux, it just lists the session-bridge up <role> commands to run in your own terminals.

The env-var method below still works and is the right choice when roles don't map to fixed local paths (e.g. spawned/remote checkouts).


Connect two sessions

To put a session in a room, give it two values when you launch it:

Variable

Meaning

Example

BRIDGE_PROJECT

the room name

shop

BRIDGE_ROLE

this session's nickname

backend

# Terminal 1 — the backend of project "shop"
BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude

# Terminal 2 — the frontend of project "shop"
BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude

Both used project shop, so they're in the same room and can talk.

Two ways to set the variables

Pick one — but don't mix them up; this is the #1 beginner mistake.

Way A — on the same line (simplest, recommended):

BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude

The variables apply only to that one claude. Nothing to clean up.

Way B — set first, then launch. On separate lines you must use export:

export BRIDGE_PROJECT=shop
export BRIDGE_ROLE=frontend
claude

⚠️ The trap: BRIDGE_PROJECT=shop on its own line without export sets a shell variable claude will not see — the bridge silently won't connect. Use export (Way B), or put everything on one line (Way A).

Tip — make aliases so you don't retype this. In ~/.zshrc (or ~/.bashrc):

alias shop-back='BRIDGE_PROJECT=shop BRIDGE_ROLE=backend claude'
alias shop-front='BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude'

Using tmux

tmux changes nothing about launching — each pane is its own shell, like a separate terminal. Set the variables in each pane and run claude:

tmux
 ├ pane 1:  BRIDGE_PROJECT=shop BRIDGE_ROLE=backend  claude
 ├ pane 2:  BRIDGE_PROJECT=shop BRIDGE_ROLE=frontend claude   ← same room as pane 1
 └ pane 3:  BRIDGE_PROJECT=blog BRIDGE_ROLE=backend  claude   ← different room

tmux matters most for auto mode: when a pane is open, the auto (default) and tmux drivers wake it directly (see the spawner). A session launched inside tmux registers its pane on its own via the SessionStart hook — no manual setup. Skip tmux entirely and auto still automates things; it just resumes or spawns a headless session instead of nudging a live pane.

If you use export (Way B) in a pane and later launch a different project's claude in that same pane, the old export lingers. Prefer Way A inside tmux to avoid surprises.


Send and receive messages

Once two sessions share a room, this just works:

  1. In the backend session, when you have something worth sharing:

    "Send the new /users response shape to the frontend session."

    It calls the bridge_send tool to deliver the message.

  2. In the frontend session, the next time you type anything, the message is automatically added to its context — no copy-paste.

That's the default ("bus") mode: sending is deliberate (the model shares a real result, not a wall of text), and receiving is automatic on the next turn. You're still driving.

You can also be explicit in either session: "Check the bridge for new messages" (bridge_recv) or "Send this to infra: staging is ready" (bridge_send).

If a session finishes and has nothing worth sharing, it simply doesn't send — nothing hits the bus and any ping-pong ends naturally. Idle sessions don't spam the room.


Roles are just labels

A role is any string you want — not only backend/frontend. Add as many sessions to a room as you like:

BRIDGE_PROJECT=shop BRIDGE_ROLE=backend  claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=web      claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=infra    claude
BRIDGE_PROJECT=shop BRIDGE_ROLE=qa       claude

When sending, address a specific role (to: "infra") or broadcast to everyone else (to: "*"). A message addressed to frontend is delivered only to frontendinfra/qa never even see it, so they can't act on it by mistake. (Only to: "*" reaches everyone.) So a multi-step flow is natural:

backend finishes an API → tells web → web updates the UI → tells infra → infra redeploys.

Whether that chain runs automatically is up to the spawner below — and for deploys, read Be careful with auto-deploy first.


Run several projects at once

Use different project names; they never see each other's messages:

BRIDGE_PROJECT=shop BRIDGE_ROLE=backend  claude   # room "shop"
BRIDGE_PROJECT=blog BRIDGE_ROLE=frontend claude   # room "blog" — isolated

Isolation is structural (messages live under a per-project folder), so shop and blog can't cross over.


Auto mode: the event spawner

Everything above keeps you in the loop — a session acts only when you give it a turn. The event spawner is an optional background program (a daemon) that removes that step: when a role receives a message, the spawner wakes that role automatically to act on it, so the sides can ping-pong with no human in between. You run it in its own terminal; it's opt-in and easy to turn off.

Drivers: auto / tmux / resume / spawn

The driver decides how a role is woken:

auto (default)

tmux

resume

spawn

What it does

Per role: live tmux pane → nudge it; else a stored session id → resume it headlessly; else fresh spawn (its returned session id is stored, so the next wake resumes)

Types into your already-open live session (tmux send-keys)

Headless claude -p --resume <id> — continues the same conversation (falls back to a fresh spawn if no id is on record yet)

Fresh claude -p process every event, always

Context

Preserved, whichever path it takes

Preserved — the same live session continues

Preserved — same conversation

None — the spawn driver never resumes, even once an id exists

Uses claude -p?

Only when it falls back to resume/spawn

No

Yes

Yes

Needs tmux?

No — uses it when a live pane is registered, works fine without

Yes

No

No

One rule resume shares with auto: neither ever resumes a session that is currently open interactively — resuming a live conversation from the side would clash with it, so those messages just arrive via the recv hook on that session's next turn.

Analogy: tmux taps an employee already at their desk; resume calls a contractor back and hands them the file they left off on; spawn hires a new temp who starts from scratch every time. auto is the dispatcher — try the desk, then the returning contractor, then a new temp — and it remembers who to call next time.

Why auto is the default: it gets tmux's benefits (watch, interrupt, no claude -p) whenever a pane is open, and falls back to a continuing headless conversation — not a cold start — when it isn't. tmux is no longer required to get context-preserving automation. Pick one of the other drivers only if you want a single behavior everywhere: tmux to force live-pane-only (skip roles with no open pane), resume to always continue headlessly, spawn for a guaranteed clean slate on every wake.

Configure and run

A role is auto-woken once it has a target — either a configured cwd, or a session record left behind by any bridged session that has run there before (hooks installed is enough; no explicit spawner set required):

# tmux / auto drivers: just open the role's session inside tmux — its pane
#   (and session id) auto-register via the SessionStart hook.
# resume / spawn drivers, or auto with no pane open yet: tell it where the
#   role's code lives:
session-bridge spawner set shop backend cwd=/path/to/backend

session-bridge spawner on              # enable globally
session-bridge spawner off shop        # ...or per project
session-bridge spawner off shop web    # ...or one role
session-bridge spawner status          # inspect current config

(resume/auto can also pick up cwd automatically from a role's session record once any session has run there — set it manually only for a role that's never been opened, or to force the spawn driver's target.)

(session-bridge is the CLI installed with this package. If it's not on your PATH, run node /ABSOLUTE/PATH/dist/cli.js spawner ....)

Run the daemon in its own terminal:

session-bridge spawner run             # foreground; Ctrl-C to stop
# or background it:
nohup session-bridge spawner run > ~/.claude/bridge/spawner.log 2>&1 &

Config is re-read on every event, so on/off and other changes take effect live — no restart needed.

Safety rails

Rail

Default

What it does

maxHops (loop guard)

6

A per-project chain counter caps consecutive auto-wakes, resetting after a quiet gap. Bounds runaway ping-pong (works for every driver).

rateLimitPerMinute

12

Max auto-wakes per role per minute.

sensitive roles

infra, qa

Never auto-woken unless explicitly enabled — a misrouted message can't trigger a deploy.

no auto-commit

on

git commit/push/reset blocked for headless drivers (spawn/resume, and auto whenever it wakes headlessly); live/tmux sessions need the block-git hook (see below).

single-flight / cooldown

One live run per role at a time for headless wakes (spawn, resume, or auto's headless fallback); a re-nudge cooldown for tmux nudges (tmux driver, or auto's live-pane path).

target required

The tmux driver never wakes a role with no live pane; spawn/resume (and auto's headless fallback) need a cwd from config or a prior session record, or the role is skipped.

staleSessionHours

24

A live-flagged session record older than this is treated as crashed, so resume/auto may take it over instead of deferring to it.

Sensitive roles are off by default

infra and qa ship in defaultOffRoles — the spawner will not auto-wake them even when configured. You must opt them in by name.

Why? The sender chooses who a message goes to, and the sender is a language model. If it ever broadcasts (to: "*") or mis-addresses, infra/qa could receive a request not meant for them — and those roles do the riskiest things (deploys, releases, destructive tests). Keeping them off is a guarantee that does not depend on the model addressing correctly: a misrouted message just sits in the inbox until a human opens that session.

Turn them on (if you want full automation):

session-bridge spawner on shop infra      # explicit opt-in (required)

Read Be careful with auto-deploy first. To change the list, edit defaultOffRoles in <BRIDGE_ROOT>/spawner.config.json.

Never auto-commit (and how to allow it)

By default, auto-driven sessions can't commit. Two layers enforce it:

  1. Headless drivers (spawn, resume — and auto whenever it falls back to either): every headless launch adds --disallowed-tools "Bash(git commit:*)" "Bash(git push:*)" .... Deny rules beat allow/bypass, so the model physically can't commit.

  2. Live sessions (tmux driver, or auto's live-pane path): a live session's turn isn't launched with --disallowed-tools, so install the block-git PreToolUse hook (in Install). It denies git commit/push/reset in any session, however its turn was triggered.

A CLAUDE.md reminder ("don't commit") is a nice-to-have but not enough for autonomous loops — a model can drift. The hook/deny rules are the real guarantee.

If you WANT commit/push automation (some people do), opt out:

  • Don't install the block-git hook, and

  • Remove git entries from denyTools (session-bridge spawner config) — it applies to every headless launch (spawn and resume alike).

  • Or narrow what's blocked with BRIDGE_BLOCK_GIT (a regex; e.g. allow commit but still block push).

⚠️ Auto-push is outward-facing and hard to undo. If you allow it, push to a feature branch and protect main (branch protection). Don't auto-push to main.

Be careful with auto-deploy

⚠️ Commits/pushes are blocked — but deploying is not git. kubectl apply, vercel deploy, terraform apply, an SSH deploy script — none are blocked by default, so an autonomous infra session could run them. Deploying is outward-facing and hard to undo; doing it with no human present is risky.

If you want infra in the loop, pick one:

  1. Keep infra manual (default — it's off). Messages queue; you open the session and deploy yourself.

  2. Block deploy commands (add them to denyTools or the block-git regex).

  3. Automate only safe steps — staging/dry-run automatic, production manual.


Change settings from inside Claude

You don't have to edit JSON or remember CLI flags. The MCP server lets you read and change every automation setting from inside a Claude session — a dashboard, conversational or via /mcp.

Safety: admin-gated. Settings can only be changed from a session launched with BRIDGE_ADMIN=1 (your "control" session). Auto-driven sessions don't have it, so a runaway loop can't flip its own safety settings. Reading config is always allowed.

# your control / dashboard session
BRIDGE_PROJECT=shop BRIDGE_ROLE=control BRIDGE_ADMIN=1 claude

Two ways to drive it:

A. Just talk to it (the model calls the tools):

"Show the bridge config." · "Set frontend to auto." · "maxHops 3, driver tmux." · "Turn automation off."

B. /mcp prompts (slash commands that apply the change on the spot):

/mcp__session-bridge__show-config       show current settings
/mcp__session-bridge__set-mode          project, role, auto|manual
/mcp__session-bridge__configure-role    cwd / model / tmuxTarget
/mcp__session-bridge__set-driver        auto | tmux | resume | spawn
/mcp__session-bridge__automation        on | off  (master switch)
/mcp__session-bridge__set-limits        maxHops / rateLimitPerMinute

Pick one, fill the arguments, and it writes the change immediately (admin session only). The daemon picks it up live.


Reference

Tools (callable by a session)

Tool

Admin?

Purpose

bridge_send(to, body)

no

Send to a role, or "*" to broadcast

bridge_recv()

no

Pull + consume unread messages

bridge_peek()

no

Preview unread without consuming

bridge_tail(limit?)

no

Inspect recent messages

bridge_roles()

no

List roles in the project

bridge_whoami()

no

Show this session's project + role

bridge_config()

no

Show automation settings

bridge_mode(project, role, auto|manual)

yes

Set a role's mode

bridge_set(project, role, {...})

yes

Configure a role (cwd/model/tmuxTarget/permissionMode)

bridge_settings({...})

yes

Global knobs (driver/maxHops/rate/…)

CLI

# setup (targets the active config dir: $CLAUDE_CONFIG_DIR or ~/.claude)
session-bridge install [--no-block-git] [--config-dir <dir>]
session-bridge doctor

# inspect the bus (use any role as your identity)
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge roles
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge send frontend "hello"
BRIDGE_PROJECT=shop BRIDGE_ROLE=cli session-bridge tail 20
session-bridge root            # bus storage directory

# the spawner
session-bridge spawner set <project> <role> cwd=<path> [model=<m>] [tmuxTarget=<t>]
session-bridge spawner on  [project] [role]
session-bridge spawner off [project] [role]
session-bridge spawner status
session-bridge spawner run [--replay]

Environment variables

Var

Required

Default

Meaning

BRIDGE_PROJECT

yes

Project (room) name — the isolation boundary

BRIDGE_ROLE

yes

This session's role (nickname)

BRIDGE_ADMIN

no

off

1 lets this session change automation settings

BRIDGE_ROOT

no

~/.claude/bridge

Where messages/config are stored

BRIDGE_BLOCK_GIT

no

commit|push|reset

Regex of git ops the block-git hook denies

BRIDGE_AUTOSEND

no

off

1 enables the Stop auto-send hook

BRIDGE_SEND_TO

no

*

Default recipient for auto-send

BRIDGE_HOP

no

0

Loop-guard hop (set on every headless launch — spawn or resume)

Optional: auto-send every turn

Off by default — deliberate bridge_send is preferred. To broadcast a session's last message automatically each turn, set BRIDGE_AUTOSEND=1 and add the Stop hook to settings.json (dist/hooks/send.js). Override the target with BRIDGE_SEND_TO. Noisier and more tokens than deliberate sends.

How it works (for the curious)

Messages and config are plain files under BRIDGE_ROOT:

~/.claude/bridge/
  spawner.config.json          automation settings (edited live)
  <project>/
    <role>.inbox.jsonl         append-only log of messages for that role
    .cursors/<role>.cursor     how many messages that role has read
    .sessions/<role>.json      session record — id/cwd/tmux pane/live flag,
                                kept fresh by the SessionStart/SessionEnd hooks;
                                the tmux/resume/auto drivers read it to find
                                a live pane or a session id to resume

bridge_send appends to the recipient's inbox. The receive hook reads your inbox from your cursor forward, injects the new lines, and advances the cursor — so you never see a message twice. Isolation is just the per-project folder.

Tests

npm test             # MCP bus end-to-end
npm run test:config  # config dashboard + admin gate
npm run test:prompts # /mcp prompts apply changes (admin-gated)
npm run test:spawner # spawn-driver ping-pong + loop guard
npm run test:tmux    # tmux-driver ping-pong + loop guard
npm run test:real    # real two-session test using `claude -p`

License

MIT

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
3wRelease cycle
2Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/devpark435/claude-session-bridge'

If you have feedback or need assistance with the MCP directory API, please join our Discord server