shabadoo

Your agent stopped to ask a question
twenty minutes ago.

Claude Code sessions run for a long time and then stop dead on a prompt โ€” a permission dialog, a plan approval, a yes/no. You walk away, come back, and find it spent nineteen of those minutes waiting for you to press Enter.

shabadoo is a self-hosted control plane for every Claude Code session on every machine you use. One dashboard. A queue of the ones waiting on you. A notification when one has been stuck. And the ability to answer from wherever you are.

One static binary for macOS and Linux, or a container for the coordinator. No Go toolchain, no clone, and deliberately no curl | sh.

The bottleneck is not the model

It is the round trip back to your attention. Multiply one blocked session by the number of projects you have going, across a workstation and a laptop and a box under the desk, and the cost is not compute โ€” it is you, not being there.

  1. 0s

    A session blocks

    Each host's agent classifies every pane. A session waiting on a prompt is flagged the moment it happens โ€” no polling from your browser.

  2. 90s

    Your phone buzzes

    Not before. Ninety seconds of grace means the prompts you were already there to answer never notify at all โ€” and the question itself is the notification's first line, not "a session is waiting".

  3. then

    You answer from anywhere

    Open the pane, see what you are agreeing to, send the keystroke. From a browser or a phone, over your own private network โ€” and the timer resets.

The blocked-session queue, and what reaches your phone A panel headed "Waiting on you" listing three blocked sessions, longest first. Each row shows an alias, a PROMPT badge, the session's own status where it has set one, the node, how long it has been idle, and underneath, the question the session is waiting on. homelab on wsl at four minutes twelve seconds asks whether to create deploy.sh. iptv on ovh, saying it is waiting on a peer, at two minutes one second, asks whether to proceed with a six-step plan. notes on laptop at eighteen seconds shows no question, because none could be recognised. An arrow labelled "after 90s" leads to a phone whose notification opens with the question itself. Waiting on you 3 homelab PROMPT wsl 4m 12s Do you want to create deploy.sh? iptv PROMPT waiting on a peer ovh 2m 01s Proceed with the 6-step plan? notes PROMPT laptop 18s after 90s Do you want to create deploy.sh? homelab ยท wsl
The list is everything waiting on you right now, longest first โ€” a session appears the moment it blocks. The 90 seconds decides only what is worth interrupting you for. Permission prompts and edit approvals โ€” the common ones, and the ones you most want on a phone โ€” carry their question. Where it cannot be recognised nothing is shown rather than something guessed, which is why one row here has none. And seeing the question is not the same as being safe to answer it: read the pane before agreeing to anything consequential.

Install it

Prebuilt binaries and a container image are published for every tagged release. Neither needs a Go toolchain or a clone.

Paste this to an agent

If you already have Claude Code or a similar agent on the machine, this is the whole install. It is the same four commands below, read out by something that can see the output.

Install the shabadoo CLI on this machine.

1. Work out my platform:
     OS=$(uname -s | tr '[:upper:]' '[:lower:]')      # linux | darwin
     ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
2. Download these two, into a directory I choose (ask me, default ~/bin):
     https://github.com/alexj212/shabadoo/releases/latest/download/shabadoo-$OS-$ARCH
     https://github.com/alexj212/shabadoo/releases/latest/download/SHA256SUMS
3. VERIFY the checksum before running anything. Show me the published digest and
   the one you computed. If they differ, delete the download and STOP โ€” do not
   continue and do not try another mirror.
4. chmod +x it, run `shabadoo version`, and show me the output.
5. Run `shabadoo setup` and summarise what it changed. It is idempotent and backs
   up anything it replaces, so tell me if it reports anything other than
   "installed" or "unchanged".
6. Then tell me how to start a session in this folder.

Show me each command before you run it. Do not pipe anything into a shell.

Everything it will do is in the box. It fetches no instructions โ€” an earlier draft began "read this URL and follow it", which asks you to trust a document you have not seen and cannot watch change. This is readable in ten seconds and decidable before anything runs.

It also gives back the two things piping into a shell structurally cannot have: every command visible before it runs, and a checksum that is actually checked rather than skipped because verifying mid-pipe is awkward. Step 3 says to delete and stop rather than retry, because "it did not match, so I fetched it again" is the wrong instinct and worth pre-empting.

This is not safer than running the commands yourself.

It is the same four steps with a model reading them out, and it adds one trust assumption: the model. It is as safe and considerably faster โ€” where curl โ€ฆ | sh is faster and less safe. If you would rather not have a model in your install path, the commands are immediately below and always will be.

Or run the commands yourself

OS=$(uname -s | tr '[:upper:]' '[:lower:]')          # linux | darwin
ARCH=$(uname -m | sed 's/x86_64/amd64/; s/aarch64/arm64/')
BASE=https://github.com/alexj212/shabadoo/releases/latest/download

curl -fsSL -o shabadoo "$BASE/shabadoo-$OS-$ARCH"
curl -fsSL -o SHA256SUMS "$BASE/SHA256SUMS"

# Verify before running it. The whole point of a published checksum is that
# somebody checks it, and this is a binary you are about to give a terminal.
grep " shabadoo-$OS-$ARCH$" SHA256SUMS | sed "s/shabadoo-$OS-$ARCH/shabadoo/" | sha256sum -c -

chmod +x shabadoo && ./shabadoo version

sha256sum is shasum -a 256 on macOS. Platforms: linux-amd64, linux-arm64, darwin-amd64, darwin-arm64.

macOS builds are unsigned.

curl does not set the quarantine attribute, so the commands above work as written. A binary downloaded through a browser is quarantined and Gatekeeper will refuse it โ€” clear it with xattr -d com.apple.quarantine shabadoo.

Or run the coordinator as a container

About 27 MB, a static binary on Alpine running as UID 1000. Built for linux/amd64 and linux/arm64 โ€” the coordinator's natural home is a small always-on box, and a lot of those are Raspberry Pis. The image is the coordinator only: an agent drives its host's tmux, so an agent in a container would have nothing to manage.

# resolve the current release, then pin what you resolved
VER=$(curl -fsSL -o /dev/null -w '%{url_effective}' \
      https://github.com/alexj212/shabadoo/releases/latest | sed 's|.*/v||')

docker pull ghcr.io/alexj212/shabadoo:$VER

latest does track the newest release, but pin a version anyway. This image drives every pane on every connected machine, so "whatever was pushed most recently" is not a property a restart should pick up on its own. Resolving the tag and then pinning it gives you both: you know which version you took, and a restart cannot quietly change it.

A coordinator you can actually reach is a few more lines โ€” a data directory, an auth posture, one pairing code. examples/docker-compose.yml is exactly that, commented, and takes about a minute. The Traefik variant is the same thing behind TLS, annotated with the one proxy setting that otherwise breaks it silently.

Why there is no curl โ€ฆ | sh

This binary installs a toolchain, and a project whose own rule is never fetch from the network during install should not open by asking you to pipe an unread script into a shell. Two commands and a checksum is the honest version of the same convenience.

Or build from source, which needs only a Go toolchain.

How it works

One static binary, three roles. A coordinator you run somewhere always-on, an agent on each machine that has tmux, and the launcher that starts the sessions in the first place.

# 1. the coordinator, on something always-on
shabadoo hub --device-tokens --bootstrap --addr 0.0.0.0:8787

# 2. the agent, on every machine you work on
shabadoo setup --service --coord https://coordinator.example

# 3. the launcher, which starts the sessions
cd ~/projects/thing && shabadoo attach

That is the whole install. Everything after it is the operator CLI โ€” sessions, tail, keys, audit โ€” which is the same API the dashboard uses and is listed in the guide.

Agents dial out

Your laptop needs no inbound port, no tunnel, no static address. It works behind NAT, on hotel wifi, asleep half the day. Only the coordinator has to be reachable.

One binary, no dependencies

Copy a single file to a fresh machine and it installs the services, the launcher and the config โ€” with no network and no source tree. No Node, no Python, no Docker required.

Sessions talk to each other

Each session is the expert in its own project. They message each other by domain โ€” send to "homelab" โ€” see who is online, and say what they are working on.

Everything is audited

Every keystroke sent into every pane is recorded and attributed to an enrolled device. Read-only enrolment exists for anything that only needs to watch.

Many sessions, working as one

The premise is not a web terminal. It is that you already run several Claude sessions, each holding context nobody else has, and they should be able to act as one system โ€” monitored and driven from anywhere, without any of them leaving your network.

CapabilityWhat it does
Route by domain Hand a task to whichever session owns a project. Ambiguous names are refused rather than guessed; unknown ones bounce with the list of what exists.
Durable inbox Mail waits for a session that is offline and is delivered when it returns. Draining is final, so nothing is silently redelivered.
Declared status tmux can tell you a window is idle. Only the session can tell you it is idle because it is waiting on a peer.
Blocked-session queue Everything waiting on a human, longest first, at the top of the dashboard โ€” rendered only when it is not empty.
Read what it said The conversation itself, not a screen scrape: turns from Claude's own transcript, with tool calls collapsed to a name and a short input and expanded on tap. Most of a transcript is that machinery rather than text, which is why a terminal view of it reads badly on a phone and this does not.
A board, not a console Needs you ยท In flight ยท Waiting on others ยท Closed. Deliberately read-only: a card maps to a line somebody wrote by hand, and writing that back from a parsed model would delete the reason written beside it. Tapping one opens the pane.

And what every project is waiting on

The same question one level up. Not is this session blocked but what is the whole estate waiting on, and who owns each of those โ€” which is the question you start asking at about the third machine.

Each project keeps a MISSION.md: a headline, what it is doing now, and a list of what it is waiting on where every line names an owner. That owner is the entire mechanism. It lets the list be grouped by who is blocked rather than by project, so you read your own rows and stop, instead of scanning all of it to find the two that are yours.

๐Ÿ“‹ Open items โ€” 12 across 4 projects

๐Ÿ”ด Waiting on you โ€” 3
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ docs-site  โ”‚ pick a name for the config flag before I document it โ”‚   2d  โ”‚
โ”‚ api        โ”‚ [prompt] Do you want to create deploy.sh?            โ”‚   4m  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”ต Waiting on laptop โ€” 1
โšช Open, nobody blocked โ€” 8

   โœ… 6 closed in the last 7 days ยท typically stood 4h

Nothing here is inferred. An item is open because it is a line somebody wrote and has not deleted โ€” a session or a person โ€” and it closes when they remove it. Prompts a session is stuck on and work handed between sessions are merged into the same grouping, so there is one list to read rather than three.

The trend is a median, not a mean. One blocker left over a holiday drags a mean somewhere useless while the typical case has not moved. And an age is bounded by how long the coordinator has been up โ€” so it says that, rather than presenting a bounded number as if it were the whole history.

Read this before you run it

Not fine print. If any of this is not acceptable to you, this is the wrong tool and it is better to find that out here.

Reaching the dashboard means driving every pane.

Whoever holds a credential can read every project path, read the full buffer of any pane, read any Claude session transcript, and send keystrokes into any pane. Those panes typically run claude --dangerously-skip-permissions, so a keystroke can do anything Claude can do on that machine.

Read-only enrolment limits a client to watching, and is the right default for anything that does not need to type. It mitigates this; it does not eliminate it.

The coordinator concentrates that reach, deliberately.

Authorising an agent is the same decision as handing that machine's Claude panes to anyone who can reach your coordinator. A private network โ€” a VPN or a tailnet โ€” is the simplest way to mean that. If it must be reachable from the internet, put it behind an identity proxy: it speaks Cloudflare Access natively. What it must never be is a bare public port, which is why it refuses to start without an auth posture at all.

File browsing is confined to the projects a node already has sessions in.

Not to a directory you configure โ€” to the set of projects that node is already reporting. An unknown one is refused with the list it does serve. The check runs on the agent, next to the filesystem, and the coordinator does not repeat it: two copies of a rule drift, and the one that matters is the one holding the file handle.

Paths are confined by resolving them rather than inspecting the string โ€” symlinks are followed first, then the result must still be inside the root. That makes .. and a symlink inside the project pointing at /etc the same check, and only one of those is caught by a filter. "Not there" and "not allowed" return the same error, so a refusal cannot be used to probe what exists outside the root.

It is bounded by design and tested against traversal. It is still a read surface you are trusting with a machine.

Everything is self-hosted. There is no cloud service, no account, and no telemetry: your prompts, panes and transcripts never leave your machines.

So if somebody hands you this, they see nothing of what you do with it โ€” you run your own coordinator and it reports to nobody, including whoever gave you the binary. The reverse is equally true and matters more: if you enrol on their coordinator instead, they see everything above. Which of those two you are doing is the whole question, and it is decided by whose address you paired with.

What it is not

LimitationDetail
Requires tmux Sessions must run in tmux. If you use Claude Code in an IDE or a bare terminal, this is not for you.
macOS and Linux No native Windows agent. WSL works.
One user Multi-tenancy is implemented but barely exercised. Treat it as single-operator.
Notifications via a relay Blocked-session alerts go through an Apprise relay to Telegram, Pushover and similar. A native iOS client exists, but the coordinator has no push sender and has never delivered a notification to it, so alerts arrive through the relay.