Onboard a new machine
How to use this doc
Read the "What lives where" table once to orient. Then paste the prompt at the bottom into a fresh Claude Code session in the cloned repo β it walks the agent through tool checks, BW bootstrap, memory seeding, and the session-recording hook. Markdown source: doc/devtasks/onboard-new-machine.md.
What lives where
| Thing | Where |
|---|---|
| Azure OpenAI endpoint, deployment, key1/key2 | BW Azure OpenAI β sponic-openai-eastus2 (gpt-image-2), id 41461676-1c12-4bc0-be89-b43f00f78928. Endpoint https://sponic-openai-eastus2.openai.azure.com/, API 2025-04-01-preview. |
| Azure SP (subscription Contributor) | BW Azure β claude-code-sp (Service Principal), id 40b98339-3dce-4cec-9eaf-b43f00f43e80. Sub 785e237b-β¦, tenant 80f5e99d-β¦. |
R2 token (sponic-images) | BW Cloudflare R2 β sponic-images, id 0a573694-8b9d-44f1-9600-b43f011a48cf. Account 394b5de6β¦. Public host pub-3a79691572bc4abc93fce31910fe320b.r2.dev. |
| CF Pages/Workers/D1 | BW Cloudflare β Wrangler API (Sponic Gardens compute), id 842043db-7664-4d40-aaaf-b43f013a211c. |
| Supabase Mgmt API (SponicControl) | BW Supabase mgmt - SponicControl, id c8a5e31d-8c73-4f38-adfe-b43b016b80c2. Project ref xumcmantignrocihtrdx. (psql is unreachable β use the Mgmt API.) |
| Resend (transactional email) | BW Resend β Sponic Gardens Email, id 87184c25-8884-497c-94d2-b41600586a6e. Key in custom field API Key (Full Access). |
| Agent DevOps SOP (how we work with Claude) | docs/devtasks/agent-devops-sop β the canonical workflow doc. Lanes, five skills (/brainstorm, /feature, /journal, /ship, /wrap), build-journal pattern. Read this before any non-trivial work. Recap below. |
| Claude skills (auto-loaded from repo) | .claude/skills/<name>/SKILL.md β brainstorm, feature, journal, ship, wrap, devtask, taskmaster. Committed; come down with git pull. Nothing to install. |
| Image-gen wrapper | apps/control/src/lib/image-gen.ts. All image generation routes through this (Azure β R2 β public.images). Lint script fails CI on direct calls. |
| Sessions D1 (live) | Worker claude-sessions.sponicgarden.workers.dev, D1 37ba42be-b8cf-4e33-bb9e-268aca325978. Auth Bearer alpaca-sessions-2026. Schema includes user/machine/title. |
| Service-access recipes (load-bearing) | infra/runbook.md β full BW unlock + per-service gotchas. Checked into git; every collaborator gets it on git pull. Grep by service name to jump to the recipe. |
| ttran β cross-machine file channel | infra/bin/ttran, folder /Volumes/PortoSams2T/ttran/ on ALPUCA. Drop-box for handing files between Claude sessions on different Macs without a code push. ttran put <file> returns a 4-char ID like AA01; ttran get AA01 from any machine that can SSH to ALPUCA fetches it. Setup section below. Recipe in infra/runbook.md β "ttran". |
BW unlock recipe (works in worktrees)
export BW_PASSWORD=$(security find-generic-password -a "[email protected]" -s "bitwarden-cli" -w)
SESSION=$(/opt/homebrew/bin/bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null)
/opt/homebrew/bin/bw get item "<item-id>" --session "$SESSION"
The shorter export BW_SESSION=$(~/bin/bw-unlock) works in a normal shell but fails inside Claude Code worktrees β always use the long form.
Windows machine setup
The recipes above are macOS-shaped. On Windows, use PowerShell (not cmd.exe β $env: syntax, ConvertFrom-Json, here-strings, and the bash here-doc patterns the rest of this doc relies on all break in cmd.exe) and substitute the equivalents below.
Toolchain β winget install
winget install OpenJS.NodeJS.LTS --silent --accept-source-agreements --accept-package-agreements --scope user
winget install Python.Python.3.13 --silent --accept-source-agreements --accept-package-agreements --scope user
winget install Bitwarden.CLI --silent --accept-source-agreements --accept-package-agreements --scope user
winget install jqlang.jq --silent --accept-source-agreements --accept-package-agreements --scope user
Skip Microsoft.AzureCLI and npm i -g wrangler unless you'll touch Azure or Cloudflare Workers directly.
After install, open a fresh PowerShell session β winget writes to per-user PATH and existing shells won't see it until restart. Verify:
node --version; npm --version; python --version; bw --version; jq --version
Gotchas
OpenJS.NodeJS.LTSactually ships Node 24 (Current), not Node 22 (real LTS). Functionally fine for this repo (above the≥20floor) but if you want true LTS, pin:winget install OpenJS.NodeJS.LTS --version 22.x.x.- npm fails with "running scripts is disabled on this system". npm ships as
npm.ps1+npm.cmd; PowerShell tries the.ps1first and the default execution policy (Restricted) blocks it. One-time fix, user-scope:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. Reversible withSet-ExecutionPolicy -Scope CurrentUser Undefined.RemoteSignedis Microsoft's recommended dev default β local scripts run, internet-downloaded scripts must be signed. pythonruns the Microsoft Store stub instead of real Python. Windows shipspython.exealiases at%LOCALAPPDATA%\Microsoft\WindowsApps\that intercept the command. Either install Python via winget first (its install dir lands ahead of the stubs on PATH for new shells), or disable the aliases in Settings β Apps β Advanced app settings β App execution aliases.curlin PowerShell resolves toInvoke-WebRequest, not the real binary. When following any curl recipe in this doc orinfra/runbook.md, usecurl.exe ...so PowerShell doesn't intercept.
BW unlock β Windows DPAPI equivalent
Windows has no macOS Keychain. The PowerShell equivalent for "stash a secret that only this user on this machine can decrypt" is DPAPI: ConvertFrom-SecureString (no -Key) produces a DPAPI-encrypted blob bound to the current user + machine.
One-time stash (run from PowerShell, after bw login <email> succeeds):
New-Item -ItemType Directory -Force "$env:USERPROFILE\.bw" | Out-Null
Read-Host -AsSecureString "Bitwarden master password" `
| ConvertFrom-SecureString `
| Set-Content -Encoding utf8 "$env:USERPROFILE\.bw\master-password.dpapi"
Daily unlock helper β drop this at %USERPROFILE%\.bw\bw-unlock.ps1:
# bw-unlock.ps1 β non-interactive BW unlock via DPAPI-stashed master password
# Usage: $env:BW_SESSION = & "$env:USERPROFILE\.bw\bw-unlock.ps1"
$ErrorActionPreference = 'Stop'
$pwFile = "$env:USERPROFILE\.bw\master-password.dpapi"
if (-not (Test-Path $pwFile)) { throw "No stashed master password at $pwFile" }
$secure = Get-Content $pwFile | ConvertTo-SecureString
$env:BW_PASSWORD = [Net.NetworkCredential]::new('', $secure).Password
try {
bw unlock --passwordenv BW_PASSWORD --raw
} finally {
Remove-Item Env:BW_PASSWORD -ErrorAction SilentlyContinue
}
Then in any new PowerShell session:
$env:BW_SESSION = & "$env:USERPROFILE\.bw\bw-unlock.ps1"
bw get item 41461676-1c12-4bc0-be89-b43f00f78928 --session $env:BW_SESSION `
| ConvertFrom-Json `
| Select-Object name, @{n='fields';e={($_.fields | ForEach-Object Name) -join ', '}}
If that resolves the Azure OpenAI item, the Windows BW chain is wired up end-to-end equivalently to the macOS recipe above.
Agent worktree sandboxes can't see your BW state
If you delegate to a sandboxed worktree agent (Claude Code, Codex), the agent's tool processes may not be able to reach %APPDATA%\Bitwarden CLI and will report bw status β unauthenticated even when your interactive shell is fine. Two ways around it:
- Shell-only workaround. Have the agent generate the BW commands and run them yourself in PowerShell, then paste the non-secret output back (item names, field name lists, etc) for the agent to verify. Don't paste session keys into the chat β they're decryption keys for everything in your vault.
- Persistent fix β bridge BW into
.env.localon disk. Runinfra/bin/bootstrap-env-local.ps1from your interactive PowerShell (where$env:BW_SESSIONis live). It fetches every secret theapps/control.env.examplereferences β Azure OpenAI, R2, CF Wrangler, Resend, Supabase service-role β and writes a populatedapps/control/.env.localto the main checkout. The worktree-env-symlink script then makes that file visible inside every worktree, so agents cancat apps/control/.env.localand get full credentials without ever touching BW. Re-run when secrets rotate.
Agent DevOps SOP β how we work with Claude
The repo ships a complete workflow for Claude Code sessions: five skills, two Stop hooks, and a build-journal pattern for multi-week projects. Nothing to install — the skills (.claude/skills/) and hooks (.claude/hooks/) are committed and auto-loaded the moment you open the repo in Claude Code.
The four-command workflow
/feature [<slug>] β build β /ship β (just stop)
(Claude journals (auto-flush hook
automatically) + auto-push hook)
/feature [<slug>]— start or resume a build. With a devtask slug (e.g./feature sentinel), resumes from the build journal and creates/attaches aclaude/<slug>-β¦worktree. With free-text (e.g./feature "fix homepage hero"), picks the right lane (worktree vs main).- Building — Claude journals decisions, blockers, phase boundaries, and env-test results automatically into the active devtask's
## Build journal. You don't need to ask. /ship— encapsulated rebase + push + deploy verify, when a worktree is ready to go live. Refuses if you're onmain.- End of session — just stop. The
wrap-on-stophook auto-flushes the journal entry;auto-push-on-stopthen pushes everything./wrapexists as an optional manual override but most sessions don't need it.
For tiny edits (typo, doc, image catalog, new devtask): skip the worktree entirely. Edit on main, just stop. The auto-push hook handles the deploy.
For the full lane policy, skill details, build-journal schema, and "Which path?" decision table, read Agent DevOps SOP. That's the canonical doc — this section is just a recap.
First day on a new machine
After the BW + session-recording setup (above), do these once to confirm the SOP machinery works on this checkout:
- Read
CLAUDE.md(loaded by Claude automatically; useful for humans too). - Verify auto-push hook is on:
grep auto-push-on-stop .claude/settings.jsonshould match. - Try
/brainstorm "I want to add X to the homepage"— confirm no plan file is created (no commits, just talk). - Try
/feature "test change"— confirm aclaude/<slug>-β¦worktree appears under.claude/worktrees/, withapps/control/.env.localsymlinked in. - Make a trivial edit inside the worktree, run
/ship, confirm live URL. - End the session — confirm the
wrap-on-stophook auto-flushes a journal entry (visible at the bottom of the relevant devtask page, or as a working-tree change todocs/devtasks/<slug>.md).
If apps/control/.env.local doesn't exist in your main checkout yet, create it from apps/control/.env.example + values from BW (Supabase URL/keys etc) before the symlink step succeeds. Worktrees inherit the symlink automatically once the main file exists.
Generate an image (canonical Azure recipe)
ITEM=$(/opt/homebrew/bin/bw get item 41461676-1c12-4bc0-be89-b43f00f78928 --session "$SESSION")
ENDPOINT=$(echo "$ITEM" | jq -r '.fields[]|select(.name=="endpoint").value')
KEY=$(echo "$ITEM" | jq -r '.fields[]|select(.name=="key1").value')
curl -X POST "${ENDPOINT}openai/deployments/gpt-image-2/images/generations?api-version=2025-04-01-preview" \
-H "api-key: $KEY" -H "Content-Type: application/json" \
-d '{"prompt":"a tiny green sprout in a terracotta pot","size":"1024x1024","n":1,"output_format":"png"}' \
| jq -r '.data[0].b64_json' | base64 -d > out.png
For anything that lands in the catalog, call generateImage() from the wrapper instead β the curl recipe is for one-off probes only.
Session recording β per-machine setup
Every session on every machine should land in the live D1 with user (OS login), machine (short hostname), and title (Claude Code's auto-generated aiTitle) populated, so the /en/devcontrol/sessions dashboard can attribute and label work correctly when multiple collaborators share the project.
The canonical hook lives in this repo at apps/control/cloudflare/claude-sessions/hooks/save-session.sh. Each machine installs a copy at ~/.claude/hooks/save-session.sh with the production URL and token baked in.
Why each user does this once
The hook file is per-machine β it lives outside the repo because it embeds an auth token and runs against your local ~/.claude/projects/ JSONLs. Pulling the repo doesn't update it. When the canonical hook changes (like the 2026-05-04 update that added user/machine/aiTitle capture and replaced the time-based throttle with content-based dedup), every machine needs to re-run the install below.
Step-by-step install (or refresh)
Run this from a checkout of the sponic repo. Works on macOS β on Linux replace sed -i '' with sed -i.
# 1. Copy the canonical hook into place.
cp apps/control/cloudflare/claude-sessions/hooks/save-session.sh \
~/.claude/hooks/save-session.sh
# 2. Bake in the production URL + token.
sed -i '' \
-e 's|API_URL="https://YOUR-WORKER.YOUR-SUBDOMAIN.workers.dev/sessions"|API_URL="https://claude-sessions.sponicgarden.workers.dev/sessions"|' \
-e 's|API_TOKEN="YOUR_AUTH_TOKEN"|API_TOKEN="alpaca-sessions-2026"|' \
~/.claude/hooks/save-session.sh
# 3. Make it executable.
chmod +x ~/.claude/hooks/save-session.sh
# 4. Confirm the values landed.
grep -E "^API_(URL|TOKEN)=" ~/.claude/hooks/save-session.sh
# Expected:
# API_URL="https://claude-sessions.sponicgarden.workers.dev/sessions"
# API_TOKEN="alpaca-sessions-2026"
Step 5 β Register the hook (one-time, only if ~/.claude/settings.json doesn't already have it)
If you've never installed this hook before, also add a Stop entry to ~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$HOME/.claude/hooks/save-session.sh",
"timeout": 15
}
]
}
]
}
}
If a Stop array already exists, append to its hooks list instead of replacing it. Use Stop, not SessionEnd β SessionEnd doesn't fire for worktree/subagent sessions and would silently lose them.
Step 6 β Restart any active Claude Code sessions
Already-running sessions don't reload settings.json or hook scripts. Quit and reopen Claude Code so the next Stop event picks up the new hook.
The harness may block writes to ~/.claude/hooks/
Some Claude Code configurations sandbox ~/.claude/hooks/. If cp fails with a permission error, run the cp + sed + chmod from a regular terminal outside Claude Code, or temporarily add ~/.claude/hooks to additionalDirectories in ~/.claude/settings.json so an agent can do it for you.
Claude Code β Windows variant
The macOS steps above assume bash resolves to the right interpreter via PATH, that .sh files execute via shebang, and that ~/.claude/settings.json user-level Stop hooks fire. None of those assumptions hold on Windows.
The combination that works on Windows (Claude Code 2.1.146, empirically verified 2026-05-21):
Step 1 β install the hook into %USERPROFILE%\.claude\hooks\save-session.sh
Same as macOS but with three sed substitutions instead of two β replace /usr/bin/python3 with python (winget Python's binary name). Run from Git Bash, anywhere inside the repo:
mkdir -p "$USERPROFILE/.claude/hooks"
cp apps/control/cloudflare/claude-sessions/hooks/save-session.sh \
"$USERPROFILE/.claude/hooks/save-session.sh"
sed -i \
-e 's|API_URL="https://YOUR-WORKER.YOUR-SUBDOMAIN.workers.dev/sessions"|API_URL="https://claude-sessions.sponicgarden.workers.dev/sessions"|' \
-e 's|API_TOKEN="YOUR_AUTH_TOKEN"|API_TOKEN="alpaca-sessions-2026"|' \
-e 's|/usr/bin/python3|python|g' \
"$USERPROFILE/.claude/hooks/save-session.sh"
chmod +x "$USERPROFILE/.claude/hooks/save-session.sh"
Step 2 β register the Stop hook in <repo>/.claude/settings.local.json, NOT user-level
Claude Code on Windows silently drops user-level Stop hooks when the project has its own Stop array; only project-scope and project-local-scope hooks fire. settings.local.json is gitignored, so this stays per-machine β every Windows collaborator adds their own.
{
"hooks": {
"Stop": [
{
"hooks": [
{
"name": "Save Claude session to DevControl",
"type": "command",
"command": "$HOME/.claude/hooks/save-session.sh",
"timeout": 15
}
]
}
]
}
}
The command must be the bash-style form $HOME/.claude/hooks/save-session.sh β matching the existing committed Stop hooks (wrap-on-stop.sh, auto-push-on-stop.sh). Claude Code on Windows auto-dispatches the command through bash, so bash.exe -c "..." wrappers, .cmd shims, and Windows absolute paths all fail silently. Forward slashes only.
Step 3 β set PYTHONUTF8=1 at User scope
So every Python process on the machine (including the hook's inline Python) defaults to UTF-8 for open(), stdin/stdout, and locale-derived encodings β preventing the cp1252 UnicodeDecodeError on JSONL transcripts that contain non-ASCII (em-dash, emoji, accented chars). Already covered in "Windows machine setup" above:
[Environment]::SetEnvironmentVariable('PYTHONUTF8', '1', 'User')
Takes effect for newly-spawned processes β including Claude Code itself after a restart, and the bash β python chain the hook invokes.
Step 4 β restart Claude Code completely
Same as macOS: running sessions don't reload settings.json or hook scripts. Quit every window and reopen.
Canonical script version cutoff (2026-05-21)
Two Windows-specific bugs in the canonical hook were fixed in commit c94a96bd: a cp1252 UnicodeDecodeError reading UTF-8 JSONL transcripts, and WinError 206 "filename or extension is too long" on the curl subprocess when transcripts are large. If you sed-installed before that commit, re-run Step 1 to inherit the fixes. Macs are unaffected.
Backfill of pre-existing local JSONLs
Claude Code writes every session transcript to ~/.claude/projects/<encoded-cwd>/<session-uuid>.jsonl regardless of whether the Stop hook ran β so any sessions you had before installing the hook are still on disk and can be replayed into D1. The hook's content-based dedup (lockfile per session ID) makes this idempotent. Run from Git Bash:
HOOK="$USERPROFILE/.claude/hooks/save-session.sh"
python <<'PYEOF'
import json, os, subprocess, time, urllib.request
from pathlib import Path
req = urllib.request.Request(
"https://claude-sessions.sponicgarden.workers.dev/sessions?user=" + os.environ.get("USERNAME","") + "&limit=200",
headers={"Authorization": "Bearer alpaca-sessions-2026"},
)
d1_ids = {r["id"] for r in json.loads(urllib.request.urlopen(req).read())["sessions"]}
projects = Path.home() / ".claude" / "projects"
hook = os.environ["HOOK"]
posted = skipped = 0
for jsonl in sorted(projects.rglob("*.jsonl")):
sid = jsonl.stem
if sid in d1_ids:
skipped += 1; continue
cwd = ""
with open(jsonl, encoding="utf-8") as f:
for line in f:
try:
d = json.loads(line)
if d.get("cwd"):
cwd = d["cwd"]; break
except: pass
payload = json.dumps({"session_id": sid, "cwd": cwd or str(Path.home())})
print(f" POST {sid} ({jsonl.stat().st_size//1024} KB)")
r = subprocess.run(
["C:/Program Files/Git/bin/bash.exe", hook],
input=payload, text=True, capture_output=True, timeout=60,
)
if r.returncode == 0:
posted += 1
time.sleep(0.3)
print(f"\nposted={posted} skipped={skipped}")
PYEOF
Verify it landed
End a turn in Claude Code (the Stop hook fires after every assistant response), then:
curl -s "https://claude-sessions.sponicgarden.workers.dev/sessions?limit=1" \
-H "Authorization: Bearer alpaca-sessions-2026" \
| python3 -c "import json,sys; s=json.load(sys.stdin)['sessions'][0]; \
print('user=', s.get('user'), ' machine=', s.get('machine'), \
' title=', s.get('title'), ' project=', s.get('project'))"
Expected: user=<your-OS-login> machine=<your-short-hostname> title=<Claude's auto-title or None for very short sessions> project=sponic. If user/machine come back as None, the hook didn't fire β confirm the Stop entry is registered and the file is executable.
Force a re-save (debug)
The hook dedups by the UUID of the last JSONL entry, stored at ~/.claude/hooks/.session-locks/<session-id>. To force the next Stop to re-upload the current session:
rm ~/.claude/hooks/.session-locks/<session-id>
List session IDs (filenames) under ~/.claude/projects/<encoded-cwd>/.
Deploying the worker (maintainer task β only when src/index.js changes)
Most collaborators never need this β installing the local hook is enough. You only deploy the worker when its code changes (e.g. accepting new POST fields, adding query params). Pushing to main does not auto-deploy the worker; it's a manual wrangler deploy against the sponicgarden compute account.
Two gotchas worth knowing first
- Wrangler 4.x walks up the directory tree looking for a "framework" and will mis-detect the parent Next.js app at
apps/control/. You must pass absolute paths for both the entry file and the config to override this. Plainnpx wrangler deployfrom the worker dir will try to build Next.js and fail. - The worker lives on the sponicgarden Cloudflare account (
394b5deβ¦), not wingsiebird. Use the API-token form below to bypass the browser login picker entirely.
# 1. Unlock Bitwarden if you don't have a session yet.
export BW_SESSION=$(/opt/homebrew/bin/bw unlock --raw)
# 2. Pull the Wrangler API token. BW item 842043db is "Cloudflare β
# Wrangler API (Sponic Gardens compute)" β has Workers/D1/Pages write.
export CLOUDFLARE_API_TOKEN=$(
/opt/homebrew/bin/bw get item 842043db-7664-4d40-aaaf-b43f013a211c \
--session "$BW_SESSION" | jq -r '.login.password'
)
# 3. Pin the account ID (sponicgarden compute).
export CLOUDFLARE_ACCOUNT_ID=394b5de665bbfdea54cdd57be9094762
# 4. Sanity check β should print "[email protected]'s Account".
cd ~/sponic/apps/control/cloudflare/claude-sessions
npx wrangler whoami
# 5. Deploy. Absolute paths matter β see gotcha #1 above.
npx wrangler deploy "$(pwd)/src/index.js" -c "$(pwd)/wrangler.jsonc"
A successful deploy ends with:
Deployed claude-sessions triggers (...)
https://claude-sessions.sponicgarden.workers.dev
Current Version ID: ...
Verify the deploy
Round-trip a probe row to confirm any newly-bound fields land:
curl -s -X POST "https://claude-sessions.sponicgarden.workers.dev/sessions" \
-H "Authorization: Bearer alpaca-sessions-2026" \
-H "Content-Type: application/json" \
-d '{"id":"deploy-probe-'"$(date +%s)"'","project":"sponic","user":"'"$(whoami)"'","machine":"'"$(hostname -s)"'","summary":"post-deploy probe"}' \
| python3 -m json.tool
curl -s "https://claude-sessions.sponicgarden.workers.dev/sessions?user=$(whoami)&limit=1" \
-H "Authorization: Bearer alpaca-sessions-2026" | python3 -m json.tool
If the second call's row has user and machine populated, the deploy is live.
Schema migrations (rare)
For a fresh DB or a new column, run the appropriate migration with --remote:
cd ~/sponic/apps/control/cloudflare/claude-sessions
npx wrangler d1 execute claude-sessions \
--file=migrations/001-add-user-machine.sql --remote
The live D1 (37ba42be-β¦) already has user, machine, title β that specific migration is a no-op against it. Future migrations should be additive and applied here before the matching worker code ships.
ttran β quick file channel between machines
ttran is a tiny SSH-backed drop-box living on ALPUCA. Any machine that can SSH to ALPUCA can put a file and get back a 4-char ID (AA01, AA02, β¦); any Claude session on any other machine can cat/get it back. Use it to hand prompts, scratch results, and pasted context between sessions without bloating the repo.
For setup and full usage, see the dedicated reference: /docs/reference/TTRAN. It's the single source of truth β covers the three-step setup (SSH access, PATH symlink, global ~/.claude/CLAUDE.md block), smoke test, day-to-day usage, the worked two-Claude-session handoff, and failure modes. Tailscale is Sponic's chosen way to reach ALPUCA; the wrapper itself is host-agnostic via TTRAN_HOST.
After setup, sanity-check the symlink and the global CLAUDE.md block:
which ttran # should print a path on $PATH
ttran help | head -1 # should start "ttran β Textfile Transfer..."
grep -c "## ttran" ~/.claude/CLAUDE.md # should print 1+
Operational recipe (BW unlock, drive paths, internals): infra/runbook.md β "ttran".
Session prompt for a new machine
Paste everything between the dividers into a fresh Claude Code session in this repo. It bootstraps the same context the primary machine has.
bw get item 41461676-β¦.
Service recipes live in infra/runbook.md β you already have it from git pull. No separate memory file to copy or seed. Grep by service name to jump to the recipe (e.g. grep -nA 30 "## Cloudflare R2" infra/runbook.md). When you discover a faster access pattern, update the runbook and commit.
Sanity check the wrapper rule: ask me "make me an image of a tiny green sprout" and verify you would route through apps/control/src/lib/image-gen.ts. Don't actually run unless I confirm.
Walk me through session recording: confirm ~/.claude/hooks/save-session.sh exists and posts to claude-sessions.sponicgarden.workers.dev. Diff it against the canonical apps/control/cloudflare/claude-sessions/hooks/save-session.sh in this repo β if they differ, walk me through the install steps in this doc (cp + sed to bake the URL/token + chmod) and remind me to restart any running Claude Code sessions afterwards. The harness may block writes to ~/.claude/hooks/, so I may need to run cp/sed myself in a regular terminal. Then run the verification curl and report whether the latest row has user/machine/title populated.Notes for the maintainer when sharing this
infra/runbook.mdis in git. It contains BW item IDs and operational recipes but no raw secrets. To onboard a collaborator: (a) add them to the GitHub repo, (b) share the BWdevops-sponiccollection with their BW account. Nothing else needs to be passed out-of-band.- The BW item IDs in this doc and in the runbook are stable references, not secrets β useless without the BW collection access.
- If a collaborator only needs read-only repo work (no service calls), skip the BW bootstrap. The agent can read code and answer questions without any of it.
Doc owner: Rahul. Last updated 2026-05-03. Markdown source of truth: doc/devtasks/onboard-new-machine.md.