deploy-runbook

deploy-runbook is a skill for Claude Code from nirholas/three.ws. It costs 38 tokens per session (2,609 once invoked), scanned A, original, Apache-2.0.

A step-by-step production deployment runbook for the three.ws application. It covers preparation, building, approval checks, scheduled-task syncing, CDN cache clearing, verification, rollback, and recovery.

In plain words
What is it for?
Use it before deploying the API, frontend, or GPU workers to production, or when a deployment fails partway through.
Why use it?
It reduces mistakes during releases by putting the required order and safety checks in one place. It also warns about stale Git worktrees consuming disk space and blocking builds.

Skill for Claude Code

Written for Claude Code: when-to-use in frontmatter. Also seen: mentions subagents.

Good fit Use it before deploying the API, frontend, or GPU workers to production, or when a deployment fails partway through.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/nirholas/three.ws/deploy-runbook
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add nirholas/three.ws --skill deploy-runbook
Clone the repo
git clone --depth 1 https://github.com/nirholas/three.ws

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for deploy-runbook

README.md
[![agentmods](https://agentmods.dev/badge/skills/nirholas/three.ws/deploy-runbook/github.svg)](https://agentmods.dev/skills/nirholas/three.ws/deploy-runbook)
Your own site
<a href="https://agentmods.dev/skills/nirholas/three.ws/deploy-runbook"><img src="https://agentmods.dev/badge/skills/nirholas/three.ws/deploy-runbook/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for deploy-runbook

Your own site · 80×15
<a href="https://agentmods.dev/skills/nirholas/three.ws/deploy-runbook"><img src="https://agentmods.dev/badge/skills/nirholas/three.ws/deploy-runbook.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 38 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 2,609 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00038 $0.02609
Opus 5.5 $0.00015 $0.01044
Sonnet 5.5 $0.00008 $0.00522
Haiku 4.5 $0.00004 $0.00261

Measured 10d ago against content hash dbd099080ce5, method: parsed. Prices are Anthropic first-party input rates as of 2026-10-07, from the pricing page.

Security

Grade A, and why

deploy-runbook scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 10d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Makes network callslowCapability

Not a fault in itself. Listed so you know the mod talks to something, and to what.

5. **Verify the deploy landed.** `curl -s https://three.ws/api/version` returns the live commit SHA + Cloud Run revision, and `npm run smoke:prod` sweeps every page declared in `data/pages.json` against the live site (`d
.claude/skills/deploy-runbook/SKILL.md · 26 lines

How it starts

The opening of the file, as written. The whole thing — 26 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Deploy runbook (API/frontend)

Remember stop-and-ask gate 2: production deploys need owner approval unless the owner's current instruction is itself the approval. Full runbook incl. LB/DNS/TLS/env/rollback/recovery: docs/ops/gcp-production.md. GPU workers deploy from their own workers/<name>/cloudbuild.yaml.

  1. Reclaim disk first: npm run clean:worktrees (add --apply to act). Deploy worktrees are created by every deploy and removed by nobody. On 2026-08-04 ten had accumulated and filled the 126 GB disk to 100%, which fails in a way that reads like a different bug entirely: git worktree add dies mid-checkout with No space left on device and leaves a half-written tree behind. The script only reclaims a linked worktree that is detached, clean, and idle past --min-age-hours (default 2, so a concurrent agent's live build is never deleted); anything with uncommitted work is reported and kept. It applies the same rules to the subagent worktrees under .claude/worktrees/, which additionally must have their worktree-agent-* branch already merged into HEAD; the merged branch is deleted with git branch -d (on 2026-10-08 nine of those held 31 GB).

  2. Prepare a CLEAN worktree: npm run prep:worktree -- --apply. That is the whole step; it encodes everything below, and running it beats staging by hand. Plan first with a bare npm run prep:worktree (writes nothing), --path <dir> to stage your own tree instead of colliding with a concurrent agent on the default /workspaces/.deploy-wt, --force to replace an existing tree (refused if it holds uncommitted work). What it does: git worktree add --detach <path> HEAD, then hardlinks every artifact in the ARTIFACTS list of scripts/prepare-deploy-worktree.mjs with cp -al on the same filesystem (not just the first). Three of them are build-critical, node_modules, chat/node_modules and character-studio/build; the optional ones (walk-sdk/dist, multiplayer/node_modules, animation-sources) are what let the worktree also run the repo's own test suite, so a pre-ship npm test can be taken at the exact commit being shipped instead of in the shared tree. It then copies BOTH .env and .env.local. Omitting either nested one kills the build minutes in: an OOM exit 144 on the avatar-studio rebuild, or Cannot find package '@sveltejs/vite-plugin-svelte' on the chat build. A nested artifact that is missing from the source tree is BUILT first (deps:chat, build:avatar-studio) rather than skipped, which is the failure a hand-run cp -al produces on a machine that has never deployed. .env.local is the one that carries DATABASE_URL, and without it step 3's migration gate cannot read the schema state and refuses to submit. Remove it when the deploy lands (git worktree remove --force <path>); that is what step 0 exists to clean up after. See docs/ops/gcp-production.md.

  3. Build: npm run build:gcp. ORDER IS LOAD-BEARING and build:gcp already encodes it. Never hand-run the steps in a different order. The full chain, in order:

    check:conflicts → check:browser-graph → check:tdz-bootstrap → ensure:avatar-studio → build:info:snapshot → docs:freshness → build:lib:full (the UMD lib) → build:avatar-sdk → build:chat → build (the frontend vite build) → publish:lib → build:info → check:dist → check:pages

    Why the order is load-bearing: check:conflicts runs first so an unresolved merge-conflict marker fails in seconds instead of after a full build. check:tdz-bootstrap sits beside it for the same reason: it refuses a browser module whose top-level bootstrap call runs above the lets that call writes. JavaScriptCore checks an assignment target's temporal dead zone before evaluating the right-hand side and V8 does not, so that ordering ships green on Chrome and throws "Cannot access uninitialized variable." on every Safari, which is how /avatars/:id went dead on iOS and macOS while every desktop check passed. build:lib:full writes dist-lib/agent-3d.js, build:avatar-sdk copies that bundle into avatar-sdk/dist/index.mjs, and the frontend vite build resolves avatar-sdk/src/agent.js against it, so both must come BEFORE build; skipping them shipped whatever stale avatar-sdk/dist the build machine happened to hold, and in a fresh worktree failed the build outright with Could not resolve "../dist/index.mjs" (2026-08-17). The frontend vite build runs with emptyOutDir, so it WIPES dist/ and everything that writes into dist/ must come after it: that is why publish:lib, which mirrors the UMD lib into dist/, follows build rather than sitting next to build:lib:full, and check:dist fails without that bundle present. publish:lib writes only the moving channels (latest, 1, 1.5) from the fresh build; every released version directory is downloaded from the write-once archive gs://three-ws-lib-releases and checked against data/agent-3d-releases.json, so the build needs network access to storage.googleapis.com, and it fails if package.json names a version that was never cut. A versioned URL is immutable and SRI-pinned by embedders, so it must never change bytes: cut a new version with npm run release:lib after bumping package.json, never by rebuilding an old one (2026-10-01: rebuilding 1.5.2 on every deploy broke every published SRI pin). check:dist also fails when a documented SRI pin disagrees with the ledger; npm run check:sri-pins -- --fix rewrites stale pins. ensure:avatar-studio consumes character-studio/build and build:chat consumes chat/node_modules, which is exactly why step 1 stages worktree artifacts. docs:freshness measures documentation drift against git history and writes public/docs-freshness.json plus its summary, so it must run before build copies public/ into dist/; running it here is what keeps /docs/freshness and every docs-page freshness badge describing the commit actually deployed instead of whenever someone last ran it by hand. Do NOT hand-run build:vercel as the frontend build: it builds the SDK/lib sub-artifacts, NOT the static HTML pages, so dist/ ends up without /, /create, etc. and check:dist fails.

  4. Submit: npm run deploy:gcp:submit. That is db:check (exit 4 if any migration is pending), then check:gcloudignore, then audit:deploy (no committed symlinks, no unsatisfied peer deps, no undeclared api/ imports, decoder assets present, and an installed node_modules that matches package-lock.json), then check:api-imports (imports every module under api/ and fails on any that throws while loading, since the server imports a handler on first hit and a load-time throw is a 500 on every request to that route rather than a build error), then the same gcloud builds submit --config server/cloudbuild.yaml --region us-central1 --project aerial-vehicle-466722-p5. check:gcloudignore simulates the upload and fails when a file the server imports at runtime would be excluded from the build context: an omitted services/home-relay/src/token.js shipped revision 00412 answering /api/healthz with ERR_MODULE_NOT_FOUND on every request (2026-09-04). The lockfile match exists because the image installs from the lockfile while every local test, and every deploy worktree that hardlinks this node_modules, runs whatever is on disk: a dependency bump nobody reinstalled shipped @x402/extensions 2.25.0 untested and every paid x402 route answered 500 on import (2026-09-17). If it fails, run npm install, then npm test, before deploying. Do NOT hand-run the bare gcloud builds submit: it is the only step that would have caught new code shipping over an old schema, and skipping it is how production ended up serving handlers that query columns their migration never applied (column "blocks_behind" does not exist, live on 2026-08-14). If the gate says migrations are pending, apply them first (npm run db:status, then npm run db:migrate); if it says DATABASE_URL is not set, step 1 did not copy .env.local. EVERY cloudbuild config must pin serviceAccount: .../three-ws-build@... (the default compute SA was deleted), and manual submits need --substitutions=SHORT_SHA=manual$(date +%s) when the config tags images with $SHORT_SHA.

  5. Sync new crons, then purge CDN: npm run deploy:gcp:sync-crons, then npm run deploy:gcp:purge-cdn. The sync creates a Cloud Scheduler job for every cron in vercel.json that has none and touches no existing job; without it a new cron never fires (four sat dead for up to three weeks until 2026-10-08). The purge is synchronous on purpose. Do not re-add --async, or post-deploy checks read stale edge content and report phantom failures.

  6. Verify the deploy landed. curl -s https://three.ws/api/version returns the live commit SHA + Cloud Run revision, and npm run smoke:prod sweeps every page declared in data/pages.json against the live site (deploy:gcp runs it automatically after the purge).

  7. Check the workers too: npm run check:worker-drift. deploy:gcp:full ships three-ws-api only. Every service under workers/ deploys from its own cloudbuild.yaml and nothing else rebuilds it, so a merged worker fix can sit unbuilt for months (remesh-service segfaulted in production on a July image while its fix waited from 2026-08-11 to 2026-11). The check is read-only and needs a gcloud login: it compares each serving image's build time and commit tag with the git history of its workers/<dir>, prints DRIFTED/drifting/current/NOT-DEPLOYED per service and region, exits 1 when any worker has carried unbuilt source past the grace window (--grace-days, default 7), and prints the exact gcloud builds submit command per drifted worker. Each of those rebuilds is a production rollout under gate 2; the (extra) region copies it lists are config-only gcloud run services update --image rolls, which are pre-approved. It shells out to gcloud and git, so it runs on the operator machine before or after a deploy, not as a Cloud Run cron.

Read the full file on GitHub · 26 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 10d ago Changed · +1 lines dbd099080ce5
  2. 1mo ago First seen · 25 lines · 38 tokens per session scan A 27ca0c27e759

Subscribe to this mod's changes

deploy-runbook is a skill published in the GitHub repository nirholas/three.ws (280 stars, last pushed yesterday), licensed Apache-2.0. It adds 38 tokens to every session and 2,609 once invoked, about $0.0002 per session on Opus 5.5. A static security scan graded it A with 1 finding (makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-10-11.

Related

deploy-runbook compared with skills from other repositories

deploy-runbook next to 6 skills from other repositories
ModTypeStarsLast pushLicenceScanTokensWorks with
deploy-runbooknirholas/three.wsSkill280yesterdayApache-2.0A38Claude Code
publish-releaseByggarepop/dotnet-mcp-orchestratorSkill225d agoMITA63Claude Code, Codex
release-changelog-harnessArenukvern/mcp_flutterSkill398todayMITA84Claude Code
aws-serverless-rollout-correctorVincentChuWaiChow/vanguard-frontier-agenticSkill242d agoApache-2.0A59Claude Code
changelog-notesmatebenyovszky/agentplaybooksSkill11yesterdayMITA35Claude Code, Codex
metagit-release-auditmetagit-ai/metagit-cliSkill411d agoMITA59Claude Code, Codex
git-wrapupcyanheads/mcp-ts-coreSkill1542d agoApache-2.0A109Claude Code, Codex