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.
npx skills add nirholas/three.ws --skill deploy-runbookgit clone --depth 1 https://github.com/nirholas/three.wsWrote 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.
[](https://agentmods.dev/skills/nirholas/three.ws/deploy-runbook)<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.
<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>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.
| Model | Per session | Once 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 |
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 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.
-
Reclaim disk first:
npm run clean:worktrees(add--applyto 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 adddies mid-checkout withNo space left on deviceand 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 theirworktree-agent-*branch already merged into HEAD; the merged branch is deleted withgit branch -d(on 2026-10-08 nine of those held 31 GB). -
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 barenpm 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,--forceto replace an existing tree (refused if it holds uncommitted work). What it does:git worktree add --detach <path> HEAD, then hardlinks every artifact in theARTIFACTSlist ofscripts/prepare-deploy-worktree.mjswithcp -alon the same filesystem (not just the first). Three of them are build-critical,node_modules,chat/node_modulesandcharacter-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-shipnpm testcan be taken at the exact commit being shipped instead of in the shared tree. It then copies BOTH.envand.env.local. Omitting either nested one kills the build minutes in: an OOM exit 144 on the avatar-studio rebuild, orCannot 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-runcp -alproduces on a machine that has never deployed..env.localis the one that carriesDATABASE_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. Seedocs/ops/gcp-production.md. -
Build:
npm run build:gcp. ORDER IS LOAD-BEARING andbuild:gcpalready 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 frontendvite build) →publish:lib→build:info→check:dist→check:pagesWhy the order is load-bearing:
check:conflictsruns first so an unresolved merge-conflict marker fails in seconds instead of after a full build.check:tdz-bootstrapsits beside it for the same reason: it refuses a browser module whose top-level bootstrap call runs above thelets 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/:idwent dead on iOS and macOS while every desktop check passed.build:lib:fullwritesdist-lib/agent-3d.js,build:avatar-sdkcopies that bundle intoavatar-sdk/dist/index.mjs, and the frontendvite buildresolvesavatar-sdk/src/agent.jsagainst it, so both must come BEFOREbuild; skipping them shipped whatever staleavatar-sdk/distthe build machine happened to hold, and in a fresh worktree failed the build outright withCould not resolve "../dist/index.mjs"(2026-08-17). The frontendvite buildruns withemptyOutDir, so it WIPESdist/and everything that writes intodist/must come after it: that is whypublish:lib, which mirrors the UMD lib intodist/, followsbuildrather than sitting next tobuild:lib:full, andcheck:distfails without that bundle present.publish:libwrites only the moving channels (latest,1,1.5) from the fresh build; every released version directory is downloaded from the write-once archivegs://three-ws-lib-releasesand checked againstdata/agent-3d-releases.json, so the build needs network access to storage.googleapis.com, and it fails ifpackage.jsonnames 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 withnpm run release:libafter bumpingpackage.json, never by rebuilding an old one (2026-10-01: rebuilding 1.5.2 on every deploy broke every published SRI pin).check:distalso fails when a documented SRI pin disagrees with the ledger;npm run check:sri-pins -- --fixrewrites stale pins.ensure:avatar-studioconsumescharacter-studio/buildandbuild:chatconsumeschat/node_modules, which is exactly why step 1 stages worktree artifacts.docs:freshnessmeasures documentation drift against git history and writespublic/docs-freshness.jsonplus its summary, so it must run beforebuildcopiespublic/intodist/; running it here is what keeps/docs/freshnessand every docs-page freshness badge describing the commit actually deployed instead of whenever someone last ran it by hand. Do NOT hand-runbuild:vercelas the frontend build: it builds the SDK/lib sub-artifacts, NOT the static HTML pages, sodist/ends up without/,/create, etc. andcheck:distfails. -
Submit:
npm run deploy:gcp:submit. That isdb:check(exit 4 if any migration is pending), thencheck:gcloudignore, thenaudit:deploy(no committed symlinks, no unsatisfied peer deps, no undeclaredapi/imports, decoder assets present, and an installednode_modulesthat matchespackage-lock.json), thencheck:api-imports(imports every module underapi/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 samegcloud builds submit --config server/cloudbuild.yaml --region us-central1 --project aerial-vehicle-466722-p5.check:gcloudignoresimulates the upload and fails when a file the server imports at runtime would be excluded from the build context: an omittedservices/home-relay/src/token.jsshipped revision 00412 answering/api/healthzwithERR_MODULE_NOT_FOUNDon 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 thisnode_modules, runs whatever is on disk: a dependency bump nobody reinstalled shipped@x402/extensions2.25.0 untested and every paid x402 route answered 500 on import (2026-09-17). If it fails, runnpm install, thennpm test, before deploying. Do NOT hand-run the baregcloud 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, thennpm run db:migrate); if it saysDATABASE_URL is not set, step 1 did not copy.env.local. EVERY cloudbuild config must pinserviceAccount: .../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. -
Sync new crons, then purge CDN:
npm run deploy:gcp:sync-crons, thennpm run deploy:gcp:purge-cdn. The sync creates a Cloud Scheduler job for every cron invercel.jsonthat 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. -
Verify the deploy landed.
curl -s https://three.ws/api/versionreturns the live commit SHA + Cloud Run revision, andnpm run smoke:prodsweeps every page declared indata/pages.jsonagainst the live site (deploy:gcpruns it automatically after the purge). -
Check the workers too:
npm run check:worker-drift.deploy:gcp:fullshipsthree-ws-apionly. Every service underworkers/deploys from its owncloudbuild.yamland nothing else rebuilds it, so a merged worker fix can sit unbuilt for months (remesh-servicesegfaulted 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 itsworkers/<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 exactgcloud builds submitcommand per drifted worker. Each of those rebuilds is a production rollout under gate 2; the(extra)region copies it lists are config-onlygcloud run services update --imagerolls, 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.
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.
- 10d ago Changed · +1 lines dbd099080ce5
- 1mo ago First seen · 25 lines · 38 tokens per session scan A 27ca0c27e759
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.
deploy-runbook compared with skills from other repositories
| Mod | Type | Stars | Last push | Licence | Scan | Tokens | Works with |
|---|---|---|---|---|---|---|---|
| deploy-runbooknirholas/three.ws | Skill | 280 | yesterday | Apache-2.0 | A | 38 | Claude Code |
| publish-releaseByggarepop/dotnet-mcp-orchestrator | Skill | 2 | 25d ago | MIT | A | 63 | Claude Code, Codex |
| release-changelog-harnessArenukvern/mcp_flutter | Skill | 398 | today | MIT | A | 84 | Claude Code |
| aws-serverless-rollout-correctorVincentChuWaiChow/vanguard-frontier-agentic | Skill | 24 | 2d ago | Apache-2.0 | A | 59 | Claude Code |
| changelog-notesmatebenyovszky/agentplaybooks | Skill | 11 | yesterday | MIT | A | 35 | Claude Code, Codex |
| metagit-release-auditmetagit-ai/metagit-cli | Skill | 4 | 11d ago | MIT | A | 59 | Claude Code, Codex |
| git-wrapupcyanheads/mcp-ts-core | Skill | 154 | 2d ago | Apache-2.0 | A | 109 | Claude Code, Codex |