bridge-sync (hybrid)
Two Claude Code instances — local (laptop) and the VPS session — each hold a
copy of the same app. This skill stops them clobbering each other: no edit
lands on either side until both have compared state and agreed on the best
version. Coordination uses Claude's built-in Remote Control (ListAgents +
SendMessage) — no relay, no tunnel. Code moves over git (preferred) or rsync.
bridge-state.sh (in scripts/) prints a compact, comparable snapshot — use it
instead of hand-gathering state.
Preconditions
ListAgents → find the VPS session (ask the user which if unclear). Confirm it
is online (Remote Control sessions show offline when not connected). If
offline, ask the user to connect it — don't reconcile blind. (Durable relay is
the offline fallback; see claude-bridge README.)
- Know the app dir on THIS host (ask once, e.g. local
~/dev/myapp, VPS
~/apps/myapp).
Coordination is turn-based
SendMessage hands control to the peer; you resume when it replies. Always name
who to reply to (your ListAgents session name) in each message.
Shared journal (MCP) — the source of truth
The journal MCP server (tools: journal_read, journal_log, claim, release,
claims_list) records what each side has done so neither acts on stale state.
who is your identity (local or vps — check via ListAgents/context).
- Start of every turn:
journal_read() to see what the peer did.
- Before editing a file or deploying:
claim(who, resource). If it returns
ok:false, the PEER holds it — coordinate over SendMessage, do NOT touch it.
- After every meaningful action:
journal_log(who, action, target, status)
(e.g. action=PUSH/DEPLOY/EDIT/VERIFY/DONE).
- When finished with a resource:
release(who, resource).
If the journal MCP isn't registered, fall back to announcing actions over
SendMessage — but prefer the journal; it survives restarts and enforces locks.
The core rule
Read journal → claim → exchange state → discuss diff → agree → apply → verify → log/release.
Never skip "agree", even for a one-liner — the peer may hold changes you can't see.
Workflow — INITIATOR (the side being edited, usually local)
- Announce + request state. Snapshot yours:
scripts/bridge-state.sh <app>.
SendMessage({to: "<vps-session>"}):
"bridge-sync : I intend . Run bridge-state.sh <your app path>
and reply your snapshot. Don't edit yet. Reply to <my-name>."
- (Turn ends.) On the VPS's snapshot, diff & classify vs yours:
git.head equal, both clean → in sync; apply your change, go to step 5.
- equal but one side dirty → uncommitted work exists; surface whose/what.
git.head differ → diverged; find common ancestor from the commit lists.
- no git → compare file hashes.
- Discuss to converge (the "best version" step).
SendMessage your reading +
proposal; let the peer push back (hotfix to preserve? whose change wins per
file?). Loop until you BOTH name the same target (a commit/branch or concrete
file set). Get an explicit "agreed" — don't assume.
- Apply on your side so your copy equals the agreed target; commit.
- Propagate the bytes (git preferred): push agreed branch/sha;
SendMessage
the VPS: "pushed , git pull + test + restart, reply result." (No shared
remote → send a git diff patch inline, or rsync via bridge-push.)
- Verify both sides (tests,
--version, health/restart); exchange results.
- Close: both
SendMessage "done, synced at , green." Report to user.
Workflow — RESPONDER (VPS, re-invoked by an incoming message)
- On a state request:
scripts/bridge-state.sh <your app> → reply the snapshot.
Edit nothing yet.
- Be honest in the discussion: uncommitted changes or a running hotfix? SAY SO —
that's exactly what this step exists to catch. Object BEFORE applying if the
plan would lose your work.
- On the agreed apply message:
git pull (or apply patch), run verification,
reply result, then "done" when green.
Moving code (bytes, not chat)
- Shared git remote — agree branch+sha; initiator pushes, peer pulls. Best.
- Patch over SendMessage — small diffs only, text; peer
git apply, then
sha/test to confirm it applied clean.
- rsync/tar over SSH —
bridge-push.sh for large trees with no shared remote.
Never assume a local path is visible to the peer — separate hosts.
Guardrails
- If state can't be compared (peer offline/silent past a reasonable wait,
STATE_ERROR), STOP and tell the user. Don't one-sidedly deploy.
- If the VPS app is running/serving, treat its copy as production: converge
toward not breaking it; restart only as an explicit agreed step.
- Keep messages tight and decision-oriented — reconciliation, not chat.
Example (MyApp, local initiates, git transport)
local: bridge-state.sh ~/dev/myapp # head=a1b9 clean
local → SendMessage(vps): "bridge-sync MyApp: adding rate-limit to /scan.
Run bridge-state.sh ~/apps/myapp, reply snapshot, don't edit. Reply to local."
vps → SendMessage(local): "head=a1b9, dirty: server.js (uncommitted CORS hotfix), service active."
local → SendMessage(vps): "commit + push that hotfix to origin/main; I'll rebase rate-limit on top. Agreed?"
vps → "agreed, pushed a1b9→c3d0."
local: (rebase, add rate-limit, push c3d0→e5f1)
local → SendMessage(vps): "git pull origin main (e5f1); npm test; restart myapp; reply result."
vps → "tests green, service active on e5f1."
both → SendMessage: "done, MyApp synced at e5f1, green."
1---2name: bridge-sync3description: Use when updating, editing, deploying, or reconciling an app that exists in BOTH your local workspace and a VPS (e.g. MyApp) and a peer Claude Code runs on the VPS. HYBRID model — the two instances coordinate over Claude's built-in Remote Control (SendMessage), and code moves over git (preferred) or rsync. Before changing either side they exchange current state, discuss the diff, converge on the single best version, apply on both, and verify. Triggers on "update <app> on the VPS", "sync local and VPS", "deploy MyApp", "what's on the VPS", "reconcile the two copies".4---56# bridge-sync (hybrid)78Two Claude Code instances — `local` (laptop) and the VPS session — each hold a9copy of the same app. This skill stops them clobbering each other: **no edit10lands on either side until both have compared state and agreed on the best11version.** Coordination uses Claude's built-in Remote Control (`ListAgents` +12`SendMessage`) — no relay, no tunnel. Code moves over git (preferred) or rsync.1314`bridge-state.sh` (in `scripts/`) prints a compact, comparable snapshot — use it15instead of hand-gathering state.1617## Preconditions181. `ListAgents` → find the VPS session (ask the user which if unclear). Confirm it19 is **online** (Remote Control sessions show `offline` when not connected). If20 offline, ask the user to connect it — don't reconcile blind. (Durable relay is21 the offline fallback; see claude-bridge README.)222. Know the app dir on THIS host (ask once, e.g. local `~/dev/myapp`, VPS23 `~/apps/myapp`).2425## Coordination is turn-based26`SendMessage` hands control to the peer; you resume when it replies. Always name27**who to reply to** (your `ListAgents` session name) in each message.2829## Shared journal (MCP) — the source of truth30The `journal` MCP server (tools: `journal_read`, `journal_log`, `claim`, `release`,31`claims_list`) records what each side has done so neither acts on stale state.32`who` is your identity (`local` or `vps` — check via `ListAgents`/context).33- **Start of every turn:** `journal_read()` to see what the peer did.34- **Before editing a file or deploying:** `claim(who, resource)`. If it returns35 `ok:false`, the PEER holds it — coordinate over SendMessage, do NOT touch it.36- **After every meaningful action:** `journal_log(who, action, target, status)`37 (e.g. action=PUSH/DEPLOY/EDIT/VERIFY/DONE).38- **When finished with a resource:** `release(who, resource)`.39If the `journal` MCP isn't registered, fall back to announcing actions over40SendMessage — but prefer the journal; it survives restarts and enforces locks.4142## The core rule43> **Read journal → claim → exchange state → discuss diff → agree → apply → verify → log/release.**44> Never skip "agree", even for a one-liner — the peer may hold changes you can't see.4546## Workflow — INITIATOR (the side being edited, usually local)471. **Announce + request state.** Snapshot yours: `scripts/bridge-state.sh <app>`.48 `SendMessage({to: "<vps-session>"})`:49 > "bridge-sync <app>: I intend <change>. Run `bridge-state.sh <your app path>`50 > and reply your snapshot. Don't edit yet. Reply to `<my-name>`."512. (Turn ends.) On the VPS's snapshot, **diff & classify** vs yours:52 - `git.head` equal, both clean → in sync; apply your change, go to step 5.53 - equal but one side **dirty** → uncommitted work exists; surface whose/what.54 - `git.head` differ → diverged; find common ancestor from the commit lists.55 - no git → compare file hashes.563. **Discuss to converge (the "best version" step).** `SendMessage` your reading +57 proposal; let the peer push back (hotfix to preserve? whose change wins per58 file?). Loop until you BOTH name the same target (a commit/branch or concrete59 file set). Get an explicit "agreed" — don't assume.604. **Apply on your side** so your copy equals the agreed target; commit.615. **Propagate the bytes** (git preferred): push agreed branch/sha; `SendMessage`62 the VPS: "pushed <sha>, `git pull` + test + restart, reply result." (No shared63 remote → send a `git diff` patch inline, or rsync via bridge-push.)646. **Verify both sides** (tests, `--version`, health/restart); exchange results.657. **Close:** both `SendMessage` "done, synced at <sha>, green." Report to user.6667## Workflow — RESPONDER (VPS, re-invoked by an incoming message)681. On a state request: `scripts/bridge-state.sh <your app>` → reply the snapshot.69 **Edit nothing yet.**702. Be honest in the discussion: uncommitted changes or a running hotfix? SAY SO —71 that's exactly what this step exists to catch. Object BEFORE applying if the72 plan would lose your work.733. On the agreed apply message: `git pull` (or apply patch), run verification,74 reply result, then "done" when green.7576## Moving code (bytes, not chat)771. **Shared git remote** — agree branch+sha; initiator pushes, peer pulls. Best.782. **Patch over SendMessage** — small diffs only, text; peer `git apply`, then79 sha/test to confirm it applied clean.803. **rsync/tar over SSH** — `bridge-push.sh` for large trees with no shared remote.81Never assume a local path is visible to the peer — separate hosts.8283## Guardrails84- If state can't be compared (peer offline/silent past a reasonable wait,85 `STATE_ERROR`), STOP and tell the user. Don't one-sidedly deploy.86- If the VPS app is **running/serving**, treat its copy as production: converge87 toward not breaking it; restart only as an explicit agreed step.88- Keep messages tight and decision-oriented — reconciliation, not chat.8990## Example (MyApp, local initiates, git transport)91```92local: bridge-state.sh ~/dev/myapp # head=a1b9 clean93local → SendMessage(vps): "bridge-sync MyApp: adding rate-limit to /scan.94 Run bridge-state.sh ~/apps/myapp, reply snapshot, don't edit. Reply to local."95vps → SendMessage(local): "head=a1b9, dirty: server.js (uncommitted CORS hotfix), service active."96local → SendMessage(vps): "commit + push that hotfix to origin/main; I'll rebase rate-limit on top. Agreed?"97vps → "agreed, pushed a1b9→c3d0."98local: (rebase, add rate-limit, push c3d0→e5f1)99local → SendMessage(vps): "git pull origin main (e5f1); npm test; restart myapp; reply result."100vps → "tests green, service active on e5f1."101both → SendMessage: "done, MyApp synced at e5f1, green."102```