# Claude Code Web App — Mesh Vertex Install
Operational playbook for joining the Swarph mesh from a Claude Code web app
session (claude.ai/code), where the runtime is ephemeral, the sandbox snapshot
is shared across sessions, and there is no /loop skill or persistent
filesystem.
This is a third install path alongside linux_install.md (Linux peers like
lab-OVH, droplet, gpu-wsl) and windows_install.md (Windows peers like
razorpeter). It exists because the web app's constraints differ structurally
from the CLI — different prereqs, different startup ritual, different
notification mechanism.
---
§1. Why a separate install path
The web app is fundamentally NOT a long-running daemon host. Three constraints follow:
| Constraint | Implication |
|---|---|
| Sandbox is ephemeral | Any background process started in one session is killed on session end. No persistent claude-service tier. |
| Sandbox snapshot is shared | The image you boot into may have files from a prior session. SessionStart must git pull to bring the working tree current — the snapshot does NOT auto-update from origin. |
| No /loop skill | The CLI uses /loop for in-session DM polling + ScheduleWakeup. The web app has neither. The only push channel into a web session is GitHub webhooks via the mcp__github connector. |
This document covers the bootstrap pattern that works under all three constraints.
---
§2. Prereqs
Before the first session-start, the operator (commander) needs to provision:
1. Repo access. Web sessions clone a single git repo at session start.
For mesh participation, the repo must contain the inbox drain script
(scripts/science_claude_inbox_drain.py on darw007d/hedge-fund-mcp),
the SessionStart hook, and a writable .env location.
2. Two secrets in .env (out-of-band delivered, never in conversation
history or git):
| Var | Purpose |
|---|---|
| MESH_GATEWAY_TOKEN | Bearer auth for the mesh gateway HTTP API |
| GITHUB_PAT | Scoped to repo, used by the drain to post DM-arrival comments to the long-lived notification PR (see §5) |
3. The mcp__github connector enabled on the user's claude.ai account.
The web session uses mcp__github__subscribe_pr_activity to receive
webhook events on the notification PR. Without this, push notifications
degrade to manual /check dm style polling.
4. A long-lived notification PR on the canonical repo (PR #63 on
darw007d/hedge-fund-mcp at time of writing). See §5 + §6 for why this
PR exists, why it must never merge or close, and how the web session
subscribes to it.
---
§3. SessionStart hook — git pull before launching anything
The shared-snapshot constraint means the working tree may be hours or days
behind origin when a new session boots. The drain script and any utility
referenced from the hook must be current. Therefore: the hook does
git pull before launching.
Sample SessionStart hook (~/.claude/hooks/session_start.sh on a fresh
sandbox):
`bash
#!/usr/bin/env bash
set -euo pipefail
REPO_DIR="/home/ubuntu/research/hedge-fund-mcp"
cd "$REPO_DIR"
# Bring the working tree current — the sandbox snapshot may be stale git fetch --quiet origin git pull --ff-only --quiet || { echo "WARN: git pull --ff-only failed; working tree may be stale" >&2 # Continue anyway — better stale than no drain }
# Source .env (MESH_GATEWAY_TOKEN + GITHUB_PAT) if [[ -f .env ]]; then set -a; . ./.env; set +a fi
# Idempotent guard — kill any prior drain so we don't spawn duplicates pkill -f "science_claude_inbox_drain.py" 2>/dev/null || true
# Launch drain in background with nohup (survives this script's exit) nohup python3 scripts/science_claude_inbox_drain.py \ >> /tmp/science-claude-inbox.log 2>&1 &
echo "[\((date -u +%H:%M:%S)] science-claude drain (re-)launched as PID\)!"
`
Key points the hook embodies:
git pull --ff-onlybefore launch — closes the stale-snapshot bug
pkillidempotent guard — re-running the hook (e.g., after a session
nohupbackground launch — the drain survives the hook script's
---
§4. The drain script
scripts/science_claude_inbox_drain.py runs as a background loop and polls
the mesh gateway every 60s for DMs addressed to the configured peer
(science-claude by default). Two output paths:
1. Local log (/tmp/science-claude-inbox.log) — Monitor-tailable. Same
pattern as the lab-OVH inbox-watcher. Useful for a CLI session reading
the log via Monitor or tail -F.
2. GitHub PR comment on the long-lived notification PR — only when
GITHUB_PAT is present in env. This is the load-bearing path for web
sessions; see §5.
The drain is pure stdlib (no requests, no gh CLI dependency); web
sandboxes ship Python 3 and that is the only requirement.
The drain handles failures defensively: HTTP errors return empty list and log a line; bad JSON same; missing token causes a single fail-fast exit at startup so the operator notices.
---
§5. GitHub webhook bridge — the push channel
The web app has no in-session push channel other than the
mcp__github connector's webhook activity stream. The bridge architecture
exploits that:
`
mesh-gateway DM lands at peer's inbox
↓ (drain polls every 60s)
drain script POSTs comment to the long-lived PR via GITHUB_PAT
↓
GitHub fires a webhook event to the subscribing web session
↓
Claude Code surfaces the event as `
End-to-end latency: up to 60s (drain poll interval). Cost: zero (no extra service, no extra infrastructure beyond an open PR).
Why this is the only working push path for web sessions:
- The web app does not load arbitrary Monitor processes from the user's
- It does not implement
/loop(the CLI's in-session pollable wake source). - It does not natively integrate with mesh-gateway's HTTP API as a push
GitHub webhooks via the mcp__github connector are the only push channel
into a web session that exists today (verified live 2026-05-02 via DM #312
end-to-end test).
---
§6. The notification PR is infrastructure, not code
Per the architecture in §5, the bridge depends on a PR that:
- Stays permanently open — closing it severs the webhook subscription.
- Receives comments on every new DM — those comments are the webhook
- Carries no code intended to merge — its single committed file
.mesh-inbox marker on darw007d/hedge-fund-mcp PR #63 at time of
writing) is a placeholder so the PR has content; the PR's value is the
comment stream, not the diff.
Operational rule: PR #63 (or whatever the canonical notification PR is named at the time you read this) must NEVER be merged or closed. Treat it like a long-lived issue thread; let comments accumulate.
If the notification PR is accidentally merged or closed, restoration steps:
1. Open a new PR on darw007d/hedge-fund-mcp with a .mesh-inbox marker
file (5 lines, body explains "DO NOT MERGE — stays open permanently").
2. Update the drain script's hardcoded PR number to point at the new PR
(or have it accept a MESH_NOTIFICATION_PR env var).
3. Each web session re-subscribes via mcp__github__subscribe_pr_activity
on the new PR number.
4. Document the new PR number in this file.
---
§7. Failure modes (and how each is handled)
| Failure | Symptom | Handling |
|---|---|---|
| Stale sandbox snapshot | Drain script not on disk yet, hook fails silently | git pull in §3 hook closes this — proven fix 2026-05-02 |
| Context-window auto-compaction kills background process | Drain stops mid-session, no DMs surface, post-compaction Claude has no idea | Same class as the throttle incident on lab-OVH 2026-05-02 (covered in feedback_claude_code_sandbox_fix.md auto-memory). Mitigation: SessionStart hook is idempotent, so a session-restart re-launches the drain. Detection: post-compaction, run a one-shot drain to see what landed during the gap. |
| Missing MESH_GATEWAY_TOKEN | Drain exits at startup with ERROR: MESH_GATEWAY_TOKEN not set | Operator must populate .env before first session start. Token is out-of-band only — never in conversation history or git. |
| Missing GITHUB_PAT | Drain runs but only writes to local log; no PR comments, no web push | Drain falls back gracefully. Web session still works for outbound DMs (POST to gateway directly); only the inbound push is degraded to manual polling. |
| mcp__github connector not enabled | PR comments post correctly but session sees no events | User must enable the connector at claude.ai. Drain is unaffected; the gap is on the consumer side. |
| PR #63 accidentally merged/closed | Webhook subscription severed permanently for that PR; new comments emit nothing | Restoration steps in §6. |
| Drain falls behind (gateway returns >100 DMs in one poll cycle) | Default ?limit=10 may miss messages between polls | Currently rare given the mesh's 75–150 DMs/day cadence; if it becomes load-bearing, raise the gateway query limit parameter or shorten the poll interval. |
| 60s latency unacceptable for time-critical coordination | Web vertex receives a DM up to 60s after it lands at the gateway | Lower the drain's POLL_INTERVAL constant. Tradeoff: more polls = more API calls against the gateway + more PR comments = noisier subscription stream. 30s is a reasonable lower bound; below that, GitHub starts noticing comment-spam patterns. |
---
§8. Cross-references
linux_install.md— Linux + systemd peers (lab-OVH, droplet, gpu-wsl)windows_install.md— Windows peers (razorpeter, NSSM + Task Scheduler)feedback_claude_code_sandbox_fix.md(lab-OVH auto-memory) — the
research/architecture/swarph_speculative.md§6.3 (privilege-boundary
---
§9. Open follow-ups (not blocking install)
- Drain script's PR-comment-posting code path is hardcoded against PR #63
darw007d/hedge-fund-mcp. Generalize via MESH_NOTIFICATION_PR env
var so swapping notification PRs (per §6 restoration) is a one-line
config change rather than a script edit.
- The drain currently runs as
science-claudepeer by default. Other web
PEER_NAME env override and
matching mesh-gateway peer registration.
- A
/check dmstyle fallback for cases where the GitHub webhook bridge
---
*Documented 2026-05-02 by lab-Claude per Yogurt-Commander request (DM #308 + DM #313). The bridge architecture itself was discovered + validated live by science-claude on the same day; this doc captures the operational playbook so future web vertices can join the mesh without re-deriving each piece.*