π°οΈ The cloud-run system, explained
How a task jumps off Matt's Mac, runs on an always-on cloud box, reports back from anywhere, and lands its finished work back on the laptop β no git or SSH knowledge required.
This is the human-readable companion to the terse docs/cloud-run-system/README.md. Everything here comes from those docs. Details that are only true on the box (never verified in the local mirror) are flagged box-only.
πWhat is this?
lifeos-devbox) as a tireless night-shift employee who never sleeps and never needs your laptop open.
You write a work order (mac-run.sh β "here's the job") and hand it over.
A wall of CCTV monitors (Mission Control) shows you what everyone on shift is doing.
A walkie-talkie (Slack) lets you answer their questions from your phone.
When the shift ends, they leave a handover report (run-done) on your desk with everything they finished.
You hand a task off from the Mac to an always-on cloud box so the run:
- survives the laptop closing or the WiFi dropping,
- is monitorable from your phone (dashboard, Claude app, Slack),
- ships itself when done β only if the task said
SHIP: trueand QA is green, - gets pulled back onto the Mac when it finishes.
Matt is non-technical about git and SSH on purpose β the system does all of that for him.
π₯οΈ The box
Hetzner CX33 Β· Ubuntu 24.04 Β· user matt Β· hostname lifeos-devbox. Always on.
π¦ Source of truth
The GitHub-synced mirror repo at /home/matt/mirror/AI-lifeOS-tools on the box β its ops/ folder is ~/ops. The local Mac copy is just a staging/planning copy and is not a git repo.
πHow a run flows, end to end
The 5 things that happen when you say /run-in-cloud
- Save & send the code. The Mac commits and pushes your project branch to GitHub (branching off
mainfirst if needed) β so the work is safe before anything leaves the laptop. - Pack the credentials. An "env-guard" step syncs any missing credential names (never the values) from the Mac to the box, and injects the report contract into the prompt.
- Wake the box. The Mac SSHes into
devboxand calls the box-side launcher~/ops/run-in-cloud.sh. - Lock, sync, launch. The box takes a per-project lock, rebases to the latest code (
git pull --rebase --autostash), and starts an interactive Claude inside atmuxsession. - Announce it. The box records the session, writes a status card, and posts a parent message to Slack
#devboxwith the run's codename and a one-line description.
(the work order)"] --> B["git commit + push
project branch"] B --> C["env-guard: sync missing
cred names Mac to box"] C --> D["ssh devbox to
~/ops/run-in-cloud.sh"] D --> E["resolve ~/mirror/<project>
+ per-repo lock"] E --> F["git pull --rebase --autostash"] F -->|conflict| G["STOP + post to Slack
never force"] F -->|clean| H["tmux: claude --remote-control
--skip-permissions"] H --> I["record session.json
+ status.sh running"] I --> J["Slack #devbox:
parent message posted"] J --> K{"cron every ~2 min"} K --> L["deploy-status.sh renders
+ wrangler pages deploy"] L --> M(("Mission Control
dashboard live")) J --> N["run posts progress /
questions as thread replies"] N --> O["Matt answers in Slack
-> relay -> tmux send-keys"] O --> N N --> P["on finish: commit + push
ship only if SHIP:true + QA green"] P --> Q["status.sh done + final
Slack report"] Q --> R["Mac: mac-pull.sh
rebase commits back"] R --> S(("run-done report
on the Mac"))
Where a run's state lives at any moment
Nothing important lives only on the Mac while a run is in flight β that's the whole point. Close the laptop and the run keeps going.
π§©The parts
launch"] MP["mac-pull.sh
pull back"] ST["setup.sh
ssh alias"] VN["vnc-view.sh
read-only view"] SY["sync-claude-to-box.sh
+ adapt-for-linux"] end subgraph BOX["βοΈ Box ops layer (~/ops)"] RIC["run-in-cloud.sh
box launcher"] STA["status.sh
write a card"] DEP["deploy-status.sh
render + deploy (cron)"] REL["slack-relay
systemd daemon"] NOT["notify.sh
legacy Telegram"] SESS["sessions/*.json
+ status/*.json"] end subgraph LIVE["π Live surfaces"] DASH(("Mission Control
Pages")) SLACK(("Slack #devbox")) APP(("Claude app
Remote Control")) end MR --> RIC RIC --> STA --> SESS SESS --> DEP --> DASH RIC --> REL --> SLACK REL --> SLACK RIC --> APP VN -.read-only.-> RIC MP -.pull.-> RIC SY -.config sync.-> BOX
Mac-side (in ~/.claude/skills/)
| Path | Role |
|---|---|
run-in-cloud/SKILL.md | Mac-half dispatcher spec; dispatches by hostname. The fullest existing spec. |
run-in-cloud/target.conf | Box target: BOX_USER, BOX_HOST (IPv4), BOX_HOST_V6, SSH_ALIAS=devbox. |
scripts/mac-run.sh | The launch: commit+push, env-guard cred sync, inject the report contract, ssh β box launcher. |
scripts/mac-pull.sh | Pull: git pull --rebase --autostash box commits onto the local branch. Reused by run-done. |
scripts/setup.sh | One-time: writes the Host devbox block into ~/.ssh/config. Idempotent. |
scripts/vnc-view.sh | Read-only VNC: tunnels box TigerVNC :1/5901 β Mac Screen Sharing; tmux attach -r. |
scripts/sync-claude-to-box.sh | rsync the behavior layer of ~/.claude to the box (never secrets), then adapt for Linux. |
scripts/adapt-claude-for-linux.sh | On box: deterministic MacβLinux path/command rewrite + an Opus-medium AI repair pass. |
run-done/SKILL.md | Mac-side pull + End-of-Run report (reuses mac-pull.sh). Read-and-report only. |
Box-side ops layer (~/ops = cloud-clone/_ops/ in the mirror repo)
| Path | Role |
|---|---|
~/ops/run-in-cloud.sh | The box-side launcher invoked over SSH. Resolves the mirror, locks, rebases, launches Claude in tmux, records the session, calls status.sh. |
_ops/status.sh | status.sh <session> <state> "<detail>" β writes ~/status/<session>.json + touches .dirty. The only write path into dashboard data. |
_ops/deploy-status.sh | cron (~2 min): if .dirty, renders ~/status/site/index.html (inline Node HTML gen) + wrangler pages deploy. Flips stale "running" cards to STALLED via live tmux ls. |
_ops/notify.sh | Older/simpler channel: plain Telegram to the hermes bot. Superseded by Slack as primary. |
~/ops/attach.sh <slug> | Open a session: live tmux if running, else --resume. |
slack-relay (systemd) | Posts the parent message + thread replies to #devbox; forwards Matt's replies β tmux send-keys. |
run-in-cloud.sh, attach.sh, and the slack-relay script + systemd unit were not found in the locally checked-out mirror β they likely live only in ~/ops on the box. Confirm with ssh devbox 'ls ~/ops; systemctl list-units | grep -i slack' before treating their exact paths as final. (This mirrors how the README flags these.)π The site
Mission Control β mission-control-devbox.pages.dev β a Cloudflare Pages project. Plain generated HTML/CSS/inline-JS, no framework, auto-refreshes every 5 min. It is regenerated and redeployed only by deploy-status.sh. The dashboard's entire source is a Node script inlined inside that one bash file β there is no separate app repo.
πΊWatching a run
#devbox is the walkie-talkie: the run radios in questions, you radio back answers from your phone, and a relay pipes your words straight into its terminal.
The 4 commands you ever need
- Launch β just tell Claude "run this in the cloud" (optionally with a handoff doc). Add a
SHIP: trueline to have it merge β main β deploy β verify on its own. - Watch β open the dashboard. That's the main check.
- Answer β reply in the run's Slack thread, or type
codename: answerin#devbox. - Pull it back β say "/run-done" when you get the finished notification.
Every way to watch, from most to least casual
# Dashboard (the main check)
open https://mission-control-devbox.pages.dev
# Phone: Claude app -> Remote Control -> session <codename>
# Slack #devbox: reply in the run's thread OR "codename: answer"
# Read-only terminal view
bash ~/.claude/skills/run-in-cloud/scripts/vnc-view.sh <slug>
ssh devbox "tmux ls" # list live runs
ssh devbox "tmux capture-pane -t <slug> -p | tail -20" # peek at an idle run
ssh -t devbox '~/ops/attach.sh <slug>' # attach (takeover); detach: Ctrl-b d
The ~2-minute dashboard cron cycle
run writes status
.dirty
waiting for next cron tick
deploy-status.sh renders HTML
wrangler pages deploy β live
A card can lag up to ~2 min behind reality β that's the cron interval, not a bug. The page itself auto-refreshes every 5 min in the browser. Stale "running" cards get flipped to STALLED after >30 min with no live tmux.
π₯Getting the work back
run-done is the report waiting on your desk: what got done in one line, the files that changed as one-click links, and suggested next steps (always including two proposed agentic loops).
When a run finishes it commits and pushes its working branch. If β and only if β the task said SHIP: true and QA is green, it also merges to main, deploys, and verifies live. Then it flips its status to done and posts the final End-of-Run report in the Slack thread.
On your side, one move brings it home:
# from inside the project repo on the Mac
bash ~/.claude/skills/run-in-cloud/scripts/mac-pull.sh # or just say "/run-done"
mac-pull.sh fetches and rebases the box's commits onto your local branch (autostashing your uncommitted edits). It never force-pushes and never deploys β pull-and-report only. The run-done skill then prints the local End-of-Run report with clickable file links and next-step suggestions.
πWhen things break
even start?"} Q1 -->|"mac-run.sh exit 2"| R1["SSH unreachable
Work safe on GitHub.
Re-run setup.sh, confirm
ssh devbox, re-invoke"] Q1 -->|"mac-run.sh exit 1"| R2["Local git problem
Nothing sent to box.
Fix git, re-invoke"] Q1 -->|"repo lock held"| R3["Run already live
Don't relaunch.
Check tmux ls / dashboard"] Q1 -->|"launched OK"| Q2{"Is the run
still alive?"} Q2 -->|"card stuck running,
no tmux"| R4["Crashed / exited
Flips to STALLED after
30 min. Inspect tmux ls,
attach.sh, status json"] Q2 -->|"rebase conflict
on box"| R5["Genuine merge conflict
Box STOPs + Slacks,
never forces. Resolve
locally, push, relaunch"] Q2 -->|"not in Claude app"| R6["Paused at a prompt
capture-pane to check,
answer in Slack"] Q2 -->|"dashboard stale"| R7["Heartbeat > 6 min
Check box cron +
deploy-status.sh log"] Q2 -->|"pull conflict"| R8["mac-pull local clash
Rebase auto-aborted,
nothing forced.
Resolve manually"]
| Symptom | Cause | Recovery |
|---|---|---|
mac-run.sh exit 2 | SSH unreachable | Work is safe on GitHub. Run setup.sh, confirm ssh devbox, re-invoke. |
mac-run.sh exit 1 | Local git problem (detached HEAD / push rejected) | Nothing sent to box. Fix git per the printed message, re-invoke. |
| "repo lock held" | A run is already live for this project | Don't relaunch. Check ssh devbox "tmux ls" / the dashboard card. |
| Rebase conflict on box | Genuine merge conflict pulling latest | Box STOPs + Slack-posts (never forces). Resolve locally, push, relaunch. |
| Card stuck "running", no tmux | Run crashed / exited | deploy-status.sh flips it to STALLED after >30 min. Inspect tmux ls, attach.sh, the status JSON. |
| Run not in Claude app | Paused at a prompt (RC only surfaces during active turns) | tmux capture-pane to check idle; answer in Slack. |
| Dashboard stale | Dispatcher heartbeat stale (>6 min) | Header banner warns; check box cron + deploy-status.sh (/tmp/mc-deploy.log). |
mac-pull.sh conflict | Local edits clash with box commits | Rebase auto-aborted, nothing forced. Resolve manually. |
| Broken hooks on box after config sync | Raw scp/rsync of ~/.claude (Mac paths leak in) | NEVER raw-copy. Use sync-claude-to-box.sh; check ssh devbox 'tail ~/.claude/adapt-log.txt'. |
π The credential ladder (a missing key never stops a run)
Name every credential a task needs by its exact env-var name in the prompt β the env-guard only syncs what it can see (*_API_KEY / *_TOKEN / *_SECRET / *_KEY). If one's missing, the box climbs this ladder:
- env-guard auto-sync β the name was declared, so it's already there.
- box logged-in Chrome β Matt's Google accounts, DISPLAY
:1, CDP:9222. - Notion vault β the "Agency Passwords and Accounts" page.
- Stop & ask in Slack β only if the action is financial / destructive / irreversible and still blocked.
Credential values are never pasted anywhere β only names travel, and the box reads secrets by grepping single keys out of its own ~/.claude/.env, never sourcing the file.
π οΈModifying the system
Change the right file for the behavior you want. This table is the map β the full version with reasoning lives in docs/cloud-run-system/README.md.
| Change kind | Touch this file |
|---|---|
| Mac launch behavior (commit/push, env-guard, report contract) | run-in-cloud/scripts/mac-run.sh |
| Box launch behavior (lock, rebase, tmux/claude invocation) | box ~/ops/run-in-cloud.sh (edit on box; mirror to _ops/) |
| Dashboard rendering / deploy | _ops/deploy-status.sh (inline Node block) |
| Dashboard data schema (card fields) | _ops/status.sh + the reader in deploy-status.sh |
| SSH target / alias | run-in-cloud/target.conf (+ re-run setup.sh) |
| Monitoring channels / operator UX doc | run-in-cloud/SKILL.md |
| Pull + End-of-Run report shape | run-done/SKILL.md (+ mac-pull.sh for git mechanics) |
| Config-sync / Linux adaptation | sync-claude-to-box.sh, adapt-claude-for-linux.sh, platform-criteria.md |
| Slack relay behavior | box-only slack-relay systemd unit + script (verify path on box) |
| Telegram notify | _ops/notify.sh + box ~/.config/hermes/hermes.env |
CHANGELOG.md in docs/cloud-run-system/ β what changed, which file, why. No silent edits.
Editing the box's ~/ops: the box mirror repo is the source of truth. Edit there (or edit local _ops/ and mirror across), commit, and keep the box's ops/ in sync. The local staging folder is not yet fully reconciled with the mirror β see PROJECT_STATE.md "Next steps".
π Deeper reading in docs/cloud-run-system/: README.md (full technical reference), MAP.md (read-me-first index), CHANGELOG.md (every change, dated).