DEV Community

Abdallah Deeb
Abdallah Deeb

Posted on

One inbox for every Claude Code session on every machine

On a normal day I have somewhere between five and fifteen Claude Code sessions running. Some are on my laptop, some on a desktop, a few on a home server that never sleeps. They run inside herdr, an agent-aware terminal multiplexer, and I attach to them over SSH from whatever I'm holding at the time, which is often a phone.

That setup works well until you step away. Then three questions start nagging:

  1. Which sessions exist right now, and which are still alive?
  2. What has each one done, what is it doing, and is it waiting on me?
  3. How do I get back into the one that needs me, from wherever I am?

None of the tools I already had answered all three. So I built a small thing that does. It's called sessionhub, it's open source (MIT), and this post is about why it exists, how it works, and how you can run it.

The sessionhub dashboard on a phone: sessions grouped by machine, and the inbox with a permission prompt waiting

The problem: every tool knows one machine, or one session

I want to be fair to the tools I use every day, because they're good at what they do:

  • herdr knows every agent on its machine and whether it's working, blocked, or done. It doesn't know about my other machines, and it knows state, not meaning: "blocked" doesn't tell me blocked on what.
  • Claude Code's own session list is per machine too. Great for resuming on the box you're sitting at; no help for the session on the server in the other room.
  • Remote Control gives a single session a link I can open in the Claude app. Excellent, but I have to turn it on per session, and I first need to know which session to turn it on for.
  • SSH plus tmux-style multiplexers get me into a machine. Then I'm hunting through panes.

What was missing wasn't another terminal. It was a registry: one place that knows every session on every machine, what each one is working on in plain words, and what each one needs from me.

What sessionhub does

sessionhub is one static Go binary with a SQLite database. The same binary is the server and every client. You run the server on one always-on machine and "join" each machine that runs Claude Code.

From there:

  • Registry. Every session on every joined machine shows up with its machine, directory, branch, title, and status: live, blocked, stale, or ended.
  • Inbox. The sessions that need you, across all machines: blocked on a permission prompt, waiting on an answer, or finished a turn you haven't looked at.
  • Remote approvals. Allow or deny a waiting permission prompt from the dashboard or the CLI. The prompt still shows in the terminal, and whichever answer comes first wins. It never adds an "always allow" rule.
  • Progress reports. Each session gets an MCP tool to report what it has done, what's in flight, and what it's waiting on.
  • Get back in. sessionhub resume <id> focuses the pane, restarts the session, or prints the exact ssh command for the machine it's on. Or turn on Remote Control and continue in the Claude app.
  • Standing rules and messages. Add a rule once and every session sees it with every prompt. Type a message into one session, or into all of them.
  • Move sessions. Hand a session, with its branch and uncommitted changes, to another machine or to a Claude Code cloud session.
  • Start new sessions. Pick a machine and a directory on the dashboard, and a new Claude session starts there with Remote Control on.
  • Alerts. An optional Telegram message when a session has been blocked or waiting on you for 30 seconds.

Here's the inbox on a phone, with a session asking to run npm install:

The inbox: a blocked session asks to use Bash for npm install, with Allow once and Deny buttons

How it fits together

Architecture: Claude Code hooks, the MCP server, and an optional herdr watcher on each machine report to one sessionhub server; you use the dashboard, the CLI, or Telegram

There are three ways a session talks to the hub, and you can use any of them:

1. Claude Code hooks. sessionhub install-hooks adds entries to ~/.claude/settings.json (it backs the file up first, and only ever touches its own entries). Most are async, so they never slow a prompt down (paths shortened):

{
  "hooks": {
    "SessionStart": [{
      "matcher": "*",
      "hooks": [
        { "type": "command", "command": "~/.local/bin/sessionhub hook session-start", "async": true },
        { "type": "command", "command": "~/.local/bin/sessionhub hook context", "timeout": 5 }
      ]
    }],
    "UserPromptSubmit": [{ "hooks": [
      { "type": "command", "command": "~/.local/bin/sessionhub hook prompt", "async": true },
      { "type": "command", "command": "~/.local/bin/sessionhub hook context", "timeout": 5 }
    ]}]
  }
}
Enter fullscreen mode Exit fullscreen mode

The context hook is synchronous on purpose: its output is how the standing rules reach the prompt. It reads a local copy of the rules and never touches the network, so a prompt never waits on the hub.

2. An MCP server. sessionhub mcp gives each session report_progress and set_title. A short snippet in ~/.claude/CLAUDE.md tells Claude when to use them:

## Reporting progress to sessionhub

- Call `report_progress` when you finish a task, when you start a task that
  will take a while, and when you are blocked waiting on me or on something
  external. Fill `done`, `in_flight`, and `waiting_on` with short one-line items.
- Call `set_title` once the session has a clear purpose, and again if the
  purpose changes.
Enter fullscreen mode Exit fullscreen mode

That's what turns "blocked" into "waiting on: permission to run npm install".

3. A herdr plugin (optional). If you use herdr, its plugin registers the sessions in your panes, ends them when the pane closes, and runs a small watcher. The watcher is what lets the hub do things on a machine: type a message, turn on Remote Control, move a session, or start a new one.

The rule I cared about most: fail open

A tool that watches your agents must never become the reason they stall. So every client fails open. Hooks always exit 0. Every call from a hook or the MCP server has a two-second limit. If the server is down, writes go to a local queue (~/.local/state/sessionhub/queue.jsonl) and drain later. Claude Code and herdr never wait on sessionhub.

The server never connects to your machines

Machines poll the server; the server never reaches into them. When you tap Allow once on your phone, the PermissionRequest hook on that machine is holding a long poll open, and it picks up your answer. When you start a new session, the machine's watcher is long-polling for work and claims the request. That keeps the firewall story simple: the only open port is the hub's.

Using it

The CLI is where I spend most of my time:

$ sessionhub ls
MACHINE  ID        TITLE                     STATUS   AGE  REPORT
tower    e5b07a1c  postgres 16 upgrade plan  live     1s   doing: writing the rollback steps
bluebox  c18e5f43  rewrite the quickstart    live     1s   waiting: your review of the wording
bluebox  a72d4b90  dark mode for settings    blocked  1s   doing: settings page
tower    3f9c1e27  fix CI token rotation     live     1s   doing: re-running the pipeline

$ sessionhub inbox
Blocked (1)
  a72d4b90  dark mode for settings  bluebox  1s  asks to use Bash: npm install

Waiting on you (1)
  c18e5f43  rewrite the quickstart  bluebox  1s  your review of the wording
Enter fullscreen mode Exit fullscreen mode

sessionhub show gives the full picture for one session, including its report history:

$ sessionhub show a72d
title:     dark mode for settings
machine:   bluebox
state:     blocked
branch:    dark-mode
resume:    ssh -t bluebox '~/.local/bin/sessionhub resume a72d4b90-...'

reports (1, newest first)
    done        theme tokens
    in flight   settings page
    waiting on  permission to run npm install
Enter fullscreen mode Exit fullscreen mode

A few more I use daily:

sessionhub approve a72d                          # allow that npm install once (asks first)
sessionhub rules add "Never push to main."       # every session sees it, on every prompt
sessionhub send --machine tower -m "Wrap up and report progress."
sessionhub move c18e bluebox                     # conversation, branch and uncommitted changes
sessionhub start tower --dir ~/Code/api -m "Fix the flaky test in ci.yml"
Enter fullscreen mode Exit fullscreen mode

That last one is the newest feature, and it's the one I wanted most. On the dashboard it's a New session button: pick a machine and a directory (it suggests the ones you've used), optionally type a first prompt, and you get an Open in Claude link a few seconds later:

The New session panel: machine tower, directory ~/Code/api, and an Open in Claude link after the start

Now I can start work on my home server from a train.

Trying it in five minutes

You can run the server and a client on one machine to see what it does:

# Install the latest release (Linux or macOS, amd64 or arm64) into ~/.local/bin:
curl -fsSL https://raw.githubusercontent.com/abdallah/session-hub/main/install.sh | sh

# This machine holds the server:
mkdir -p ~/.config/sessionhub
install -m 600 /dev/null ~/.config/sessionhub/server.toml
sessionhub server &

# Join it: creates a token, installs the hooks and the MCP server.
sessionhub join localhost --name "$(hostname -s)"

# Start claude anywhere, send a prompt, then:
sessionhub ls
sessionhub login --name my-phone   # a one-time sign-in link for the dashboard
Enter fullscreen mode Exit fullscreen mode

For more machines, run the server on an always-on box (there's a systemd unit and a docker-compose.yml), put it behind a tunnel or reverse proxy, and sessionhub join each machine. The self-hosting guide walks through it.

A word on security

Be honest with yourself about what this server can do. A machine token or a signed-in browser can approve tool calls, add rules that every session reads, type messages into sessions, and start new ones. That's the point of the tool, and it also means a leaked credential can get Claude to run commands on your machines.

So: keep the server on a private network if you can, use HTTPS if you expose it, turn on the Telegram alerts (they also fire whenever someone adds a rule), and use the per-machine opt-outs (remote_permissions = false, remote_start = false) on machines you want to keep hands-off. SECURITY.md has a table of what each credential can do.

How it was built

I built sessionhub with Claude Code itself, which turned out to be a good test of the tool and of the process.

Every feature started as a short spec, then a plan broken into tasks, and each task had to meet a written definition of done before it counted. Two rules from that list did most of the work:

  • Don't invent APIs. Claude Code hook payloads and herdr events come from captured real payloads in testdata/, never from memory. The spec said it bluntly: verify against --help and the docs, and quote what you found.
  • Tests walk the whole path. Each one checks the start state, acts, asserts what must not have happened as well as what must, and reads the result back the way you would see it.

The early plan was to host it on Cloud Run. An always-on instance came out at about $45–50 a month, so it moved to a systemd user service on a home server behind a Cloudflare Tunnel, which costs nothing extra.

What's next

sessionhub is a personal, single-user tool: one person, their machines, no accounts or teams. That's deliberate, and I don't plan to change it. Things I do want:

  • a Claude Code plugin, so the hooks, MCP server and CLAUDE.md snippet install in one step;
  • cloud sessions reporting in, so the claude.ai/code sessions show up next to the local ones.

If you run more than a couple of Claude Code sessions across more than one machine, I'd love to hear whether this matches your workflow, and where it doesn't.

Repo: https://github.com/abdallah/session-hub (MIT, Linux and macOS binaries on the releases page)

Top comments (1)

Collapse
 
ricart_juncadella_d62f385 profile image
Ricart Juncadella •

Having all three answers on a phone makes the cross-machine inbox useful beyond the desk. When viewing it remotely, is the read API authenticated by sessionhub, or is access control expected to come from the deployment?