cd /news/developer-tools/show-hn-hoop-a-sandboxed-p2p-live-co… · home topics developer-tools article
[ARTICLE · art-67769] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Show HN: Hoop – A sandboxed P2P live collaboration harness for Claude Code

Hoop, a sandboxed P2P live collaboration harness for Claude Code, is now available as an open-source plugin that runs entirely in Docker containers, requiring no host installation of Claude Code or Node.js. The tool provides an interactive setup wizard, a local web dashboard with live sessions and event search, and ships its own Claude binary with a separate account, enabling collaboration on bare machines with just Docker. Creator Bruno de Queiroz designed Hoop to install, document, and observe third-party MCPs and skills without re-implementing them, supporting features like memory, code-graph RAG, and observability.

read19 min views3 publishedJul 21, 2026
Show HN: Hoop – A sandboxed P2P live collaboration harness for Claude Code
Image: source

hoop-showcase.mp4 #

Being alone is not a requirement. A sandboxed and web harness on top of Claude Code. Two deliverables in one install:

— an interactive wizard that wires a curated stack (memory, code-graph RAG, automation, platform MCPs, docs RAG, observability, design, second-brain)./hoop:setup

— a containerized local web dashboard (Next.js,/hoop:dashboard

http://localhost:7842/) with live sessions, a skill browser with one-click triggers, a nested sub-agent tree, push-based event observability, and BM25 + optional semantic search across all events.

The plugin does not re-implement any of the third-party MCPs or skills it installs. It picks, installs, documents, and observes them.

There are two ways in. Pick the one that matches your host.

/plugin marketplace add bruno-de-queiroz/hoop
/plugin install hoop@hoop-marketplace
/plugin list
/reload-plugins
/hoop:setup

(The /plugin list

  • /reload-plugins

dance is required on Claude Code v2.1.138 to activate a freshly pre-seeded plugin in the current session. New sessions don't need it.)

You do not need Claude Code (or even Node) installed on the host. The sandbox ships its own claude

binary and authenticates with its own Claude account, so a bare machine with Docker is enough.

Prerequisites

Docker— Docker Desktop, or any Docker engine + Compose v2 (docker compose

). This is theonly hard dependency forhoop start

— Claude Code, Node, gh, uv, gws, and the dashboard all live in containers, never on your host, and the sandbox seeds its own Claude profile on boot (sohoop start

needs no host jq). A fewothersubcommands do shell out to small host tools — see the optional list below.git andbash— to clone the repo and run the CLI.** A web browser you can reach**— for the one-time login approval. It does** not**have to be on the same machine; you just need to open a URL and copy a code back (see step 4). Handy for headless servers.A Claude account with an active plan(Pro / Max / Team / Enterprise) that can sign in via/login

.jq*(needed by some subcommands, not*—hoop start

)required byhoop setup

andhoop logout

, which edit JSON on the host (the wizard reads your gh/Claude identity intoprofile.md

; logout rewrites the sandbox credentials).hoop open

uses it too but degrades with a warning if it's absent. Install: macOSbrew install jq

; Debian/Ubuntusudo apt-get install -y jq

.curl*(optional — never strictly required)*— used byhoop setup

to probe Docker Model Runner and by the dashboard health-wait; both fall back to a bash/dev/tcp

probe when it's missing. Install:brew install curl

/sudo apt-get install -y curl

.awk*(needed only for*— required byhoop mount

)hoop mount add

/remove

to rewrite the mounts list; unused by every other command.Docker Model Runner*(optional)*— for semantic-search embeddings.hoop setup

enables it (Docker Desktop AI, or thedocker-model-plugin

on Docker Engine) and appends the embedding model to the compose stack via Compose'smodels:

element (Docker Compose v2.38+), sohoop start

auto-pulls it and wires the endpoint into the sandbox. If DMR is ever unreachable — or Compose is older than v2.38 —hoop start

transparently drops the override and falls back to BM25 rather than failing. Skippable — Ollama, OpenAI, or a custom OpenAI-compatible endpoint also work.

Run hoop doctor

at any time to verify the host has everything and see how close you are to a clean Docker-only setup.

Steps

git clone https://github.com/bruno-de-queiroz/hoop && cd hoop

./plugins/hoop/cli/hoop.sh install      # then open a new shell so `hoop` resolves

hoop start

hoop setup

hoop login

Then open ** http://localhost:7842/**. Because the dashboard only trusts localhost, your browser is recognized as the host automatically — the access token is minted for you, nothing to paste. (Remote access is deliberately gated; see

Architecture.)

That's the full loop: hoop start

hoop setup

(optional) → hoop login

→ open the dashboard. A few notes for the standalone path:

Configure your stack with This is the interactive wizard (memory, code-graph RAG, automation, platform MCPs, docs RAG, semantic search, observability, design, second-brain, telemetry isolation) — the same one behindhoop setup

./hoop:setup

, but native to the CLI so it needs no host Claude Code. It runsbeforehoop login

(writing MCP config needs no auth). For one-off additions later,hoop add mcp/plugin/skill

(see theCLItable) persists into the sandbox profile across rebuilds.Terminal-only?hoop open

runs an interactiveclaude

in a throwaway sandbox over your current directory — no dashboard, no browser needed after login.Updating:git pull

thenhoop rebuild

picks up new code into both images.Signing out / switching accounts:hoop logout

clears the sandbox's Claude login so a different account canhoop login

.

hoop setup

(or /hoop:setup

, which points you to it) walks you through these steps with one consent at the top, then installs each pick — auto-runnable and secret-taking MCPs run immediately (every command printed first); browser-login, plugin-marketplace, and host-CLI options are printed as guided steps to finish yourself:

# Step Pick
1 Consent Y / N
2 Detect prior state (read-only)
3 Memory claude-mem (installed automatically — no choice)
4 Code-graph RAG (if you code) Serena / claude-context / code-graph-mcp / Cognee / skip
5 Automation n8n-mcp yes / no
6 Platform MCPs multi-select: Atlassian, Google Workspace, GitHub, incident.io, Slack
7 Docs RAG Context7 yes / no
8 Observability Sentry / Datadog (multi-select)
9 Design Excalidraw yes / no
10 Second-brain Obsidian (3 flavors) / Notion (2) / Logseq / NotebookLM
11 Sign-ins Claude Code /login , then gh (device flow) + any queued MCP OAuth — all inside the sandbox

Each run appends to the sandbox profile's ~/.claude/hoop/sandbox/profile/.claude/hoop/install-log.md

(also viewable from the dashboard) so re-runs are auditable. Secrets never reach the log — they go straight to claude mcp add -e

or the 0600 ~/.claude/hoop/hoop.env

.

/hoop:dashboard

(or start | stop | restart | rebuild | status | logs

) runs the dashboard inside a container. Your host only needs Docker Desktop — no Node, no npm install

, no Next.js build pollution. Each verb takes an optional service target (all

(default) · sandbox

· dashboard

); start

builds lazily (only when an image is missing) while rebuild

always rebuilds.

Pairing (inviting a teammate to co-drive a session) uses ** cloudflared** to expose the local dashboard over a public tunnel — it's

baked into the dashboard image, so nothing to install on the host. The dashboard runs fine without pairing; only share links start a tunnel.

Five panels:

Sessions— fs.watch on~/.claude/sessions/

; updates in real time.Skills— every skill on disk (user + plugin), filterable, with a one-click "Run" that spawnsclaude -p '/<name>'

inside the dashboard container and streams stdout back to the panel.Sub-agents— nested tree reconstructed from PreToolUse / PostToolUse events on theAgent

tool. Click a node to see its prompt, tool calls, and final output.Events— chronological live tail via Server-Sent Events. Hooks push each event to the sandbox's/ingest

over the Unix domain socket; the dashboard tails the resulting stream with zero polling.Search— opens with ⌘K. BM25 (FTS5) always works; semantic search (sqlite-vec, 768-dim embeddings) activates when an embedding backend is configured. Set one up viahoop setup

//hoop:setup

— recommended isDocker Model Runner(added to the compose stack via Compose'smodels:

element and pulled onhoop start

), with Ollama (nomic-embed-text

), OpenAI, or any custom OpenAI-compatible endpoint as alternatives. Hybrid fuses BM25 + semantic via Reciprocal Rank Fusion.

The dashboard is single-user and localhost-only by design; access is gated by a per-install token (see Architecture below).

plugins/hoop/cli/

ships a small oosh-based CLI that wraps the runtime. It lives inside the plugin (framework engine + entry point + completions + the stack engine in lib/stack.sh

) so it ships with the plugin and the slash commands (/hoop:setup

, /hoop:dashboard

) can invoke it directly. It needs no external install and resolves its own paths.

./plugins/hoop/cli/hoop.sh install     # symlink `hoop` onto PATH + shell completion (bash/zsh)
./plugins/hoop/cli/hoop.sh <module> <command>

Two levels: top-level verbs act on the whole stack; modules scope a single service. All of them drive one engine (cli/lib/stack.sh

) — the single source of truth for host-side preflight (profile bind-mount prep, dashboard/peer auth tokens, forwarded embedding env, compose orchestration). The Claude onboarding bypass + plugin/hook wiring run inside the sandbox on boot (sandbox/seed-profile.mjs

), so the host needs no jq. start

/rebuild

are deliberately split — start

only builds an image when it's missing (fast otherwise), while rebuild

always rebuilds so you pick up code changes.

Command What it does
hoop start · stop · restart · rebuild · status · logs
Whole stack (agent-sandbox + dashboard ) via the engine (up -d / down / build + up -d --force-recreate for the project). rebuild takes `-n --no-cache` .
hoop login · logout
Authenticate the sandbox with its own Claude account (one-time). login drops you into claude inside the running sandbox to run /login (paste-code flow — approve the URL in your host browser); the sandbox mints and self-refreshes its own OAuth token. logout clears it so you can sign in as a different account. Your host credentials are never read or copied.
Module Commands What it does
dashboard
start · stop · restart · rebuild · status · logs
Controls only the (dashboard UI container--no-deps , leaves agent-sandbox alone). rebuild takes `-n --no-cache` .
sandbox
start · stop · restart · rebuild · update
Controls only the . Lifecycle verbs run the shared engine scoped to the sandbox (so plugin wiring + forwarded env still happen); agent-sandbox containerrebuild recreates the container (`-n --no-cacheto skip the layer cache);updatepins the baked-inclaude-code version (-c --claude-version` ).
open
(default)
Runs a fresh, telemetry-isolated sandbox over the current directory: mounts $PWD read-write into the agent workspace and launches claude interactively (docker run --rm -it , real tty for the TUI). Forces HOOP_DISABLE_TELEMETRY=1 (add `-T --telemetryto allow bundled-tool telemetry) and strips the dashboard-only hooks + the hoop plugin from an overlaysettings.json, while keeping credentials, setup MCPs, skills, and other plugins. Extra args pass straight through, e.g.hoop open --model opusorhoop open "fix the failing test"` .
add
mcp · plugin · skill
Installs a component into the sandbox profile so it persists across rebuild/restart/recreate and is shared by both dashboard sessions and hoop open (the profile is bind-mounted into both). mcp <name> [flags] [-- <cmd>]claude mcp add , defaulting to --scope user so the server is global (not stranded under one project — pass your own `-s --scopeto override);plugin [-m --marketplace <spec>] <plugin[@marketplace]>claude plugin install;skill -d
mount
add · list · remove
Bind-mounts host folders into the sandbox workspace at /home/agent/workspace/<name> . `add -p --path <host-dir> [-n --name <name>]adds one (the-ppath tab-completes directories),listshows the configured mounts,remove <name>drops one. Mounts persist via a generated compose override; eachadd /remove recreates the .agent-sandbox` container
install
(default)
Bare hoop install symlinks hoop onto PATH + wires shell completion (`-f --forcereinstalls over an existing symlink). The repo itself is never modified;hoop uninstall` removes the symlink and shell wiring.
setup
(default)
Runs the interactive stack wizard (the native port of /hoop:setup ): boots the sandbox, then walks memory / code-graph / automation / platform MCPs / docs RAG / semantic search / observability / design / second-brain / telemetry, installing each pick into the sandbox profile. Auto/secret MCPs run via claude mcp add ; OAuth/plugin/host-CLI sign-ins complete at the end inside the sandbox. Needs a TTY (refuses head-less), runs before hoop login , writes profile.md + install-log.md . --reset-first wipes all sandbox state for a blank slate first.
doctor
(default)
Read-only health check of the host + stack through a Docker-only-standalone lens: verifies Docker + Compose v2, reports which optional subcommand tools (jq /curl /awk ) are present, checks the sandbox image/containers, Claude auth, and the semantic-search backend (incl. the DMR Compose models: override + reachability). Fails only on truly blocking problems; everything else is advisory.
hoop start                    # bring up the whole stack at http://localhost:7842/
hoop setup                    # interactive wizard: configure the sandbox stack (no host Claude Code needed)
hoop login                    # one-time: authenticate the sandbox with its own Claude account
hoop dashboard rebuild        # rebuild + recreate ONLY the dashboard container
hoop sandbox rebuild          # rebuild + recreate ONLY the agent-sandbox container
hoop open                     # interactive claude in a sandbox over $PWD
hoop add mcp context7 -- npx -y @upstash/context7-mcp   # install an MCP (user scope) into the sandbox
hoop add skill -d ~/.claude/skills/impeccable           # copy a local skill into the sandbox profile
hoop mount add -p ~/code/myproject                      # expose a host folder to the sandbox workspace

The add

/ mount

subcommands are also exposed as the ** /hoop:add** and

slash commands, which delegate to this CLI. Everything

/hoop:mount

add

writes lives in ~/.claude/hoop/sandbox/profile

(bind-mounted at /home/agent

), so a component installed once is available in every dashboard session and in hoop open

, and survives image rebuilds.hoop open

uses the hoop-sandbox

image (build it once with hoop sandbox rebuild

) and mounts the sandbox Claude profile (~/.claude/hoop/sandbox/profile

, -p\|--profile

to override) so claude

is already authenticated — run hoop login

once to authenticate the sandbox with its own Claude account. Unlike the dashboard's agent-sandbox

, open

runs with telemetry blackholed and without hoop's dashboard hooks/plugin (which need the dashboard's socket), so it's a clean, isolated interactive session that still has your setup MCPs and skills. hoop install

/ hoop uninstall

manage the PATH symlink and shell wiring; the repo itself is never modified.

The hoop-sandbox

image ships a **headless Chromium + the official **, registered in the sandbox profile automatically (from inside the container, via

@playwright/mcp

claude mcp add

, so the registration can't point at a browser the running image doesn't have). The agent gets browser_navigate

, browser_click

, browser_type

, browser_fill_form

, browser_snapshot

, browser_take_screenshot

, and more — driven by a browser that runs entirely in-container as the unprivileged

agent

user. No host browser, no host process, no host.docker.internal

gymnastics, and nothing runs in youruser context. Because the same image + profile back both surfaces, browser tools are available in every dashboard session

and in

hoop open

.Two deliberate hardening choices:

Ephemeral profile ( The browser profile is kept in memory and never written to disk, so no cookies or logins persist across sessions and there's no ambient auth lying around. To drive a site that needs a login, hand the browser tools a--isolated

).--storage-state

file (see the).@playwright/mcp

docsNo arbitrary-code tool.@playwright/mcp

exposesbrowser_run_code_unsafe

(arbitrary JavaScript in the Playwright process, RCE-equivalent) as a non-removable "core" capability. hoop denies it via Claude's ownpermissions.deny

in the sandboxsettings.json

, so the model never sees it — the rest of the toolset is unaffected.

/hoop:dashboard

can hand a share link to a teammate over a cloudflared

tunnel. They open it, pick a name, and the host admits them; from that point both sides see the same live transcript and can chat (>

prefix) or co-drive the model — from a laptop or a phone.

Run a turn with /plan <task>

and the sandbox forces the agent read-only: it investigates, then submits a plan that opens in a review panel. The host and any full-capability peer can drop inline comments anchored to the exact passage — synced live across everyone — then Approve or Request changes. A rejection feeds the comments back and the agent revises the plan.

7 · Iterate — “Request changes” feeds the comments back; the agent revises read-only and resubmits, and once the host approves the session leaves plan mode and implements it.

Peers co-drive the same session from anywhere — here's “Rafael” reviewing the plan from his phone:

The runtime is split across two containers so a compromise of the web layer can't reach your credentials:

(trusted) — owns theagent-sandbox

claude

binary, your Claude profile (OAuth credentials, plugins, MCP config, sessions/transcripts), the long-livedclaude -p --input-format=stream-json

subprocesses, andevents.db

(sole writer). It exposes a small HTTP API over aUnix domain socket(/var/run/hoop/sandbox.sock

) — no TCP port. The model runs as an unprivilegedagent

user, and the claude-facing plugin surface the sandbox actually uses (hook scripts + the.mcp.json

tools server + the plugin manifest) isbaked into the sandbox image at/opt/hoop

, root-owned so that user can't tamper with the hook scripts. Host-only pieces are deliberately not baked: thecommands/

(host-stack slash commands) andagents/

(host diagnostics) aren't meaningful inside the sandbox, andcatalog/

+templates/

are read only byhoop setup

running on the host. The image is self-contained — no host repo bind-mount — so it runs on a host with neither the repo at a fixed path nor Claude Code installed. (SetHOOP_PLUGIN_DEV=1

to overlay the full host repo back onto/opt/hoop

for live-editing plugin source in development.)(untrusted view) — Next.js bound todashboard

127.0.0.1:7842

, withno. Every API route is a thin proxy that calls the sandbox over the socket, so a compromised dashboard can only do what the sandbox API allows.claude

binary and no access to~/.claude

events.db

stays the source of truth: the sandbox writes it (hooks fire there), the dashboard only reads it via the socket. Three tokens gate the three hops:

Token Hop Header
dashboard.token
browser ↔ dashboard x-dashboard-token (+ cookie)
sandbox.token
dashboard ↔ sandbox x-sandbox-token
hook.token
hook scripts ↔ sandbox /ingest
x-hook-token

This is the "sandboxed agent" model: the OS-process boundary is the security boundary, and the dashboard holds no secrets. Pairing (co-driving a session with a teammate) layers on top via cloudflared

and per-peer share tokens.

~/.claude/hoop/
  hoop.env                    opt-in overrides forwarded into the sandbox (0600):
                              OPENAI_API_KEY / EMBEDDING_BASE_URL / EMBEDDING_MODEL /
                              EMBED_DIM / telemetry switches
  host-gateway.cache          cached host.docker.internal address
  shares.json                 active pairing share links
  sandbox/profile/            the sandbox's Claude HOME (bind-mounted to /home/agent)
    .claude/
      .credentials.json       the sandbox's OWN Claude OAuth token (never the host's)
      hoop/
        events.db             SQLite (FTS5 + sqlite-vec); the dashboard reads it over the socket
        events.jsonl          append-only event audit log + replay buffer if the dashboard is down
        install-log.md        audit trail of every `hoop setup` run
        profile.md            identity + installed-stack summary written by the wizard
~/.local/share/hoop/          per-install secrets: dashboard.token, peer-signing.secret (0600)

Because the sandbox's HOME is bind-mounted from sandbox/profile/

, everything the container writes to its own ~/.claude/hoop/

lands under that nested path on the host — the agent owns all runtime state; the dashboard only reads it over the socket.

The plugin does not edit your ~/.claude/CLAUDE.md

or ~/.claude/settings.json

.

hoop/
  .claude-plugin/marketplace.json        self-hosted marketplace
  plugins/hoop/
    .claude-plugin/plugin.json           manifest
    commands/                            /hoop:setup, /hoop:dashboard, /hoop:plan, /hoop:add, /hoop:mount
    catalog/                             8 install recipes (one per wizard layer)
    hooks/scripts/                       emit-event.sh (Unix-socket push, <50ms) + permission-gate.sh
    sandbox/                             trusted agent runtime (owns claude + all state)
      Dockerfile                         sandbox image (claude + Node HTTP server on a UDS)
      server.ts                          HTTP-over-Unix-socket API the dashboard proxies to
      lib/                               active-sessions, db, ingestor, embeddings, sessions,
                                         skills, agents, search, spawn, shares, peer-joins, …
    dashboard/                           untrusted view (no claude, no credentials)
      Dockerfile                         dashboard image (Next.js standalone; no claude, no compilers)
      docker-compose.yml                 agent-sandbox + dashboard services (+ optional DMR embedding model via Compose `models:`)
      app/api/*                          proxy routes → sandbox over the socket
      lib/sandbox-client/                HTTP-over-UDS client; lib/auth*, lib/peer-*  (auth + pairing)
    shared/                              logger, clamp, shutdown (used by both images)
    templates/                           profile.md + install-log.md wizard templates (read host-side)
    cli/                                 oosh-based `hoop` CLI (ships inside the plugin)
      oo.sh                              oosh framework engine
      hoop.sh                            entry point (+ hoop.comp.sh / hoop.zcomp.sh)
      lib/stack.sh                       the two-service runtime engine (preflight + compose)
      modules/                           dashboard, sandbox, open, login, logout, add, mount, setup, doctor, install, uninstall
  README.md
  LICENSE

The sandbox seeds its own profile on boot (sandbox/seed-profile.mjs

, run by the container entrypoint using the image's baked Node — no host jq): it wires settings.json

hooks on PreToolUse, PostToolUse, SessionStart, Stop, and UserPromptSubmit — declared in the sandbox profile (not in a host hooks.json

) so they only ever run inside the sandbox, never on your host. Every event runs hooks/scripts/emit-event.sh

which:

  • POSTs the JSON event to the sandbox's /ingest

endpoint over the Unix domain socket (--unix-socket

) viacurl --max-time 1

— push, not polling. (HOOP_INGEST_URL

can override the target for legacy/dev setups.) - Falls back to appending to events.jsonl

if the socket isn't reachable. - Exits in <50ms — pure bash + curl, no node/python/jq.

The sandbox is the sole writer: its ingest route persists to SQLite + FTS5 (and sqlite-vec if semantic search is enabled), then emits on an in-process EventEmitter that feeds the SSE stream the dashboard proxies to the browser. On startup, the ingestor drains events.jsonl

from its saved offset so events written while the dashboard was down replay automatically.

hooks/scripts/permission-gate.sh

(PreToolUse) is the sole tool-permission gate: it asks the sandbox over the same socket and blocks until the host (or a peer allowed to decide) responds — this is what backs /plan

's read-only enforcement and the plan-review approval flow below.

v0.1(this release): wizard, containerized dashboard, push-based event pipeline, BM25 + opt-in semantic search.** v0.2**: inject skill triggers into existing Claude sessions instead of spawning new ones; per-skill-run isolation via ephemeral containers.v0.3+: more catalog entries; non-Claude clients (Cursor, Codex) where MCPs overlap.

This is opinionated by design. PRs welcome, especially:

  • Verifying install commands on less-common platforms (Windows, NixOS).
  • Adding new catalog options with verified install recipes.

Please open an issue before changing the curation philosophy (curated menus, one-consent install, no re-implementation, single-user localhost dashboard).

MIT

── more in #developer-tools 4 stories · sorted by recency
── more on @hoop 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-hoop-a-sandb…] indexed:0 read:19min 2026-07-21 ·