Implementation log
Chronological record of the build and first deployment of Claude Remote, 2026-11-09 evening → 2026-11-10. Times are Eastern Time. Commit hashes are those of this repository; steps without a hash left no commit (work on the node, on the gateway, or verification only).
Entries state what was done and what was observed. Things not done are listed at the end.
2026-11-09
| Time | Step | Result | Commit |
|---|---|---|---|
| ~21:40 | Discovery. Live mld scan from the gateway and direct inspection of candidate nodes; historical inventories were not trusted |
29 Macs, 27 reachable; no local model endpoint, no 10 GbE, no NAS (infrastructure-inventory.md) | c50198c |
| Node selection. M5M36 chosen: always-on Mac Studio, no competing workload, already a tunnel peer | node-selection.md | c50198c |
|
| 21:47 | Scaffold. pnpm workspace, packages/shared (state machines, DTOs, wire formats, IPC types), database schema, the binding engineering contract |
CONTRACT.md, apps/server/migrations/001_init.sql |
346f04f, e2ad1fe, 8b3811d |
| Node tooling. On M5M36: tmux 3.8, ripgrep, PostgreSQL 17 (Homebrew service), Claude Code CLI 2.1.322 | installed; Claude Code not authenticated | — | |
| 21:54–21:57 | Network architecture, deployment plan, migration manifest (laptop environment classified item by item); backup/restore scripts, mld manifest generator, local CI gate | docs/*.md, deploy/*, scripts/ci.sh |
c50198c, 16ac05d, d24ea6c |
| 22:00–23:05 | Four parallel workstreams against the frozen shared contract: | ||
| · runtime-core — tmux sessions, PTY attach, durable tasks, hooks, events, host metrics | bd15514 |
||
| · runtime-fs — path safety, fs, git, search, Claude introspection, skills, MCP | bd15514 |
||
| · server — authentication (password + TOTP / passkey), sessions, WebSocket proxy, files, tasks, cluster, skills/MCP, system, OpenAPI | 6e27f12 |
||
| · web — terminal workspace, mobile toolbar, PWA shell, sessions, files/editor, tasks, cluster, skills/MCP, system, settings | d0d5579 |
||
| 22:53 | Integration against the real runtime. The server suite gained end-to-end tests that start the real runtime (own tmux socket, real claude binary, no prompt). 14 mismatches between server and runtime were found and fixed, including: RECOVERABLE detection after the tmux session is lost (a session.exit without an exit code is now resolved against the runtime), and reuse of the hook token across runtime restarts while sessions survive. scripts/dev-up.sh / dev-down.sh added |
integration suite green (16 tests at that commit, 17 now) | 8bc5803 |
| 23:06 | packageManager pin dropped (the node runs pnpm 12) |
ca47aeb |
|
| ~23:10–23:59 | Defect round. (1) Browser scrollback: tmux attach skips intermediate output, so the scrollback is now a runtime-written mirror of the pane history (core-scrollback.ts). (2) Attach-state tracking: RUNNING / DETACHED follow proxied streams immediately and are confirmed with the runtime. (3) Rate-limit buckets: page loads and health checks separated from the credential budget. (4) CSP frame-src 'self' blob: for the PDF preview |
suites green | cf56e38 |
Security finding during the build. The first conversation-id pattern would have accepted a string beginning with -- as the value after --resume, i.e. a way to smuggle an option into the claude command line. Fixed before deployment in both layers: server pattern ^[A-Za-z0-9][A-Za-z0-9-]{7,63}$ (also applied to ids reported by hooks) and a runtime check that refuses option-like --resume values |
covered by tests in both packages | fixed pattern present from 6e27f12 (server); runtime check added in 8bc5803 |
|
CI gate. scripts/ci.sh: build shared → typecheck → runtime, server, web tests → full build |
green |
Test results recorded on the laptop (macOS, real tmux, real PostgreSQL, real claude binary without
prompts): runtime 209 passed; server 175 passed, including 17 integration tests against the real runtime;
web unit 12 passed; Playwright 19 passed against the dev mock (15 Chromium desktop, 4 WebKit iPhone 15
emulation) and 2 passed against the real backend.
Migration and deployment (completed by 2026-11-10 00:05; deployment dated 2026-11-10)
Steps in the order they were carried out; individual times were not recorded.
| Step | Result |
|---|---|
| Migration to the node (per migration.md) | Copied: sanitized global CLAUDE.md (sudo password removed), jof250 skill, 7 agents, 10 jof-* commands, 5 MCP server scripts without API keys, settings limited to model + fast mode. Not copied: memory notes, MCP API keys, the laptop's auto-approve-everything PreToolUse hook, cluster slash commands |
| Node SSH key | A dedicated key generated on M5M36; authorised on the git server restricted to git-shell, and with a full shell on compute nodes M3U96a and M3U96b; CR_EXEC_HOSTS=M3U96a,M3U96b |
Manifest generated on the gateway with deploy/make-manifest.py (secrets generated there, never committed) |
pinned to M5M36; three PM2 processes |
Deploy without public route (mld) |
build, migrations, PM2 start on the node; local health 200 |
| LAN verification | 10 API routes + a POST + a WebSocket upgrade → 401 without a session; POST with a foreign Origin → 403; wrong setup token → 401; security headers present (CSP with per-document script hashes and no unsafe-inline for scripts, HSTS, nosniff, X-Frame-Options: DENY, Referrer-Policy); data directory, socket and token files 0600/0700; only listeners: node on 8620 and PostgreSQL on loopback |
| Runtime on the node | Claude Code hooks (Notification, Stop, UserPromptSubmit, SessionStart) installed in the node's ~/.claude/settings.json; the real claude 2.1.322 launched in a tmux session through the runtime and rendered its welcome screen; no prompt sent |
| Backup + restore drill | first encrypted backup produced (about 20 KB); restored into a scratch database: schema and row counts printed |
| Tunnel route | https://claude.spboucher.ai → Caddy on the gateway → WireGuard → M5M36:8620 |
| Public verification | /api/v1/health 200 publicly; valid TLS, HTTP/2, HTTP→HTTPS 308; audit log records the real client public address through the tunnel (X-Forwarded-For trusted only from the gateway) |
| Git adoption | ~/apps/claude-remote on the node is a git clone with origin = the cluster git server (claude-remote.git); the node directory is the source of truth |
| Documentation set written (this directory) | status as of 2026-11-10 00:05 |
State at 2026-11-10 00:05: service deployed and answering; mld heal (every 5 minutes) restarts missing
PM2 processes; nightly backup scheduled at 03:35. No administrator account exists yet and Claude Code
on the node is not authenticated — both are owner steps.
Deviations from the original brief, recorded as such
| Topic | What was done instead | Why |
|---|---|---|
| Dedicated OS identity | Runs under the existing cluster operator macOS account | A dedicated account breaks the cluster's mld/PM2 conventions and Claude Code's Keychain login. Isolation is by process separation, a 0600 unix socket and workspace-root confinement of the file API — not by OS user; the terminal is a full shell of that account by design |
| VPN-only access | Public HTTPS with password + mandatory second factor | Access from any phone without a client (network-architecture.md) |
| Web Push | In-app notifications only | Not implemented |
| Skills-registry install | Read-only search adapter; install answers 501 | By design for this milestone |
| Local model management | Empty panel | No local model endpoint exists on the cluster |
| Research integrations (LaTeX, Jupyter) | None as dedicated features | They work only through the terminal or tasks if the tools are installed; TeX is not installed on the node. The editor has a PDF file preview |
| Hosted CI | Local gate scripts/ci.sh |
The git server hosts bare repositories only |
Not done / not verified at the time of writing
- Real iPhone Safari and PWA standalone mode (WebKit emulation only).
- The laptop-independence test (laptop off, phone on cellular) — needs the owner.
- Passkey login in a real browser (server verified with a software authenticator only).
WAITING_FOR_INPUTfrom a real Claude conversation (hook relay tested end to end with the real hook script; no paid conversation was run).- Remote SSH tasks against real nodes from the application (the node key works by hand on M3U96a and
M3U96b; in-app only with a fake
ssh). - Project "clone" mode against the real git server (
git clonewith the node key works by hand). - MCP add / remove / test and skills enable / disable / remove against a real configuration (mock / fake only).
- The 4429 backpressure close under real saturation.
- Off-node copy of backups (they stay on M5M36).
- A real reboot of M5M36 and a rehearsal of the disaster-recovery runbook.
Open follow-ups
- Owner: first-run steps (authentication.md), including copying the backup passphrase.
- Owner: laptop-independence test; real-device mobile pass.
- Copy backups to another node; keep the passphrase elsewhere.
- Reconcile the small documentation/code gaps listed in architecture.md
("Limitations") — data-directory sub-folders named in the contract,
execAllowedsemantics, the unconfiguredlintscript.