Skip to content

petrova-host production deploys move to the deploy hook

Date: 2026-08-31 Status: closed Supersedes: none Superseded-by: none — current

petrova-host is the single largest line in the Vercel account: 23h52m of build CPU in the Aug 20–31 cycle, 29.7% of the whole. The cause is volume, not waste. main took 265 non-merge commits in eleven days — most of them hourly chore(state): scheduled sweep pushes from petrova-bot — and each one triggers a full production build that installs three times, compiles the CLI, host and dashboard, and copies the staged trees.

A path guard does not help: only 4 of those 265 commits (1.5%) touch none of its build inputs, because scripts/vercel-build.sh bakes state/, docs/, contracts/, spec/, core/ and registry.yaml into the function bundle. That guard shipped today anyway, for correctness (2026-08-31-the-host-ignore-step-skipped-on-author.md), and it is not a cost measure.

The remaining lever is deployment frequency, and this repo already owns the mechanism. .github/workflows/deploy-batch.yml flushes drifted projects once a day at 08:00 UTC through per-project VERCEL_DEPLOY_HOOK_<PROJECT> secrets, and its header states the intent: “Vercel frontends have prod auto-deploy disabled; merges accumulate as deploy drift … Clean projects are skipped — no drift, no build, no build minutes.”

petrova-host was never enrolled, and neither was anything else: the repo held no VERCEL_DEPLOY_HOOK_* secret at all, so the daily flush has been reporting-and-skipping every project since it was written. The estate believed it batched. It did not.

Disable git-triggered production deployments for main on petrova-host and deploy it through the hook instead. "git": {"deploymentEnabled": {"main": false}} lands in vercel.json rather than in the project-settings UI, so the rule is reviewable in a diff — the same reasoning that put ignoreCommand there.

deploymentEnabled is an allow-map whose unlisted branches default to enabled, so listing main: false disables exactly one branch and leaves preview deployments untouched. Previews remain the pre-production compile for this surface and are guarded by the fork-point predicate shipped earlier today.

A deploy hook named deploy-batch (governed daily flush) was created on main and stored as VERCEL_DEPLOY_HOOK_PETROVA_HOST. It was fired once before this change to prove the path: deployment dpl_EusMybsAGdcRDurz3tCLZXyzbtvs, target production, state READY, carrying deployHookId: Zx04cELi60.

The daily batch will not yet pick this project up automatically. hubble’s /api/deploy-drift computes drift from the GitHub compare API and is returning 401 on every private repository — 35 of the 71 projects it tracks, petrova-host among them, report status: "unknown" rather than "drift". deploy-batch.yml only POSTs a hook for status == "drift", so until that credential is repaired the daily run will pass over this project in silence. The compare call succeeds with a valid token, so the fault is hubble’s credential and not the repository.

The operator was shown this and chose to proceed. Until hubble is fixed, production deploys of petrova-host are a manual act: run deploy-batch via workflow_dispatch with only_project: petrova-host, or fire the hook directly. Merging to main will no longer publish, and nothing will announce that.

That is the whole risk of this decision and it is stated here rather than discovered later: a surface that has stopped deploying looks exactly like a surface with nothing to deploy.

  • Fix hubble’s token first, then flip — the safer order, and it would have repaired drift for all 35 private-repo projects at once. Not chosen; the operator took the cost reduction now.
  • Batch the state/ sweep bot instead — leaves auto-deploy alone and cuts the same volume at source, but edits a governed workflow to solve a deploy-path problem.
  • Do nothing — leaves 29.7% of the account’s build minutes on hourly generated commits.
  • Production builds for this project fall from roughly 24 a day to at most one, and to zero on days nobody runs the batch.
  • The site can lag main by up to a day by design, and further while the drift gap is open.
  • Reverting is one line: remove the git block from vercel.json.
  • The next honest number comes off the usage dashboard at the end of the cycle. Nothing is claimed here beyond the mechanism being proven.
  1. Hook proven before the flip — dpl_EusMybsAGdcRDurz3tCLZXyzbtvs, production, READY.
  2. After this merges: fire the hook once and confirm a new production deployment appears, which proves deploymentEnabled: false does not block hook-triggered builds.
  3. Then confirm the next ordinary push to main produces no production deployment.

dpl_EusMybsAGdcRDurz3tCLZXyzbtvs confirmed live via the Vercel API: deployHookId: Zx04cELi60, deployHookName: "deploy-batch (governed daily flush)", target: production, readyState: READY. The mechanism is real and fires. vercel.json’s deploymentEnabled.main: false still holds at HEAD, and no main-sourced deployment appears in the last 20 production deployments (spanning back to 2026-08-27) — criterion 3 holds.

The “Known gap, accepted deliberately” risk is now materialized, not hypothetical: zero hook-fired deployments since 2026-08-31, four days idle, because hubble’s /api/deploy-drift 401 on private repos was never repaired. This is the exact outcome this doc predicted in advance and accepted the cost of, not a new defect — closing on that basis rather than reopening.

Named plainly: the hook is called deploy-batch (governed daily flush) and it has fired once, ever. One historical deployment carries deployHookId; zero since. A job named daily that has run once is a dead cron by any other name — the same shape as CAIRNET, gate.yml, sync-docs and .panto/ in this estate’s recent history. Signing this doc on “the mechanism can fire” is correct; leaving “the daily flush isn’t daily” unrecorded is not. Belongs in whatever alerting scope this estate builds next: **no deploy-batch flush in

48h** is a cheap, checkable rule and it is the exact silence this doc named in advance.