feat(ci): gate production deploys on approved release intent - #210
Open
haydenshively wants to merge 4 commits into
Open
feat(ci): gate production deploys on approved release intent#210haydenshively wants to merge 4 commits into
haydenshively wants to merge 4 commits into
Conversation
Release intent moves from `release-<bot>` labels to a `Releases <bot>` line in the PR description, frozen into the squash commit. A new Gate job in deploy-production.yml binds the pushed commit to its merged PR and deploys a bot only if a reviewer other than the author approved the PR after the intent was added, judged at the merged_at snapshot. Refused bots get a comment and deploy nothing. - packages/ci-scripts: parser (prime-monorepo's golden fixture), body edit-history replay, pure gate, and manifest.json as the single list of CI-deployable bots - deploy-bot.yml takes package/environment from the manifest, absorbs the quoter-bot variant, and chains Release (<bot>-<PR#>) and the Slack notify after a successful deploy - release-slack-notify.yml gains workflow_call: releases created with GITHUB_TOKEN never fired its release trigger, so it had never run - production workflow_dispatch and the deploy-production concurrency group are removed; labels are synced from the body as a visual cue - publish-quoter-bot-npm.yml accepts the new job name and the old one for historical runs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Drop HTML comments before parsing intent: GitHub does not render them, so a hidden `Releases <bot>` would never be seen by the approver - Refuse every intent on a PR whose body has 100+ edits: GitHub evicts older revisions, so a removal/re-add that should reset the intent clock can no longer be proven absent - Move the Docker Hub image publish into deploy-bot.yml behind a manifest `publishImage` flag, so a sibling bot's failed deploy in a multi-bot release cannot skip quoter-bot's image - Re-sync labels on labeled/unlabeled so a hand-removed release label is restored to match the description Same-bot rapid-release ordering (pre-existing per-bot concurrency behavior) is deferred as BOTS-128. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
haydenshively
added this pull request to stack #212
September 10, 2026 03:38
haydenshively
marked this pull request as ready for review
September 10, 2026 03:58
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0147667a89
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- Fail the deploy when the manifest package matches no workspace member (`--fail-if-no-match`), so a stale name cannot tag a deploy that never ran - Drop an unterminated `<!--` through end of text, which GitHub renders as a comment, so hidden intent cannot ride a later approval - Verify on every run that the newest body revision equals the current body: `userContentEdits.diff` is a full snapshot today (checked live on PR #210), and the gate now refuses rather than misdates if that ever changes Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
cashd
approved these changes
Sep 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Release intent moves from⚠️ comment and deploy nothing.
release-<bot>labels to aReleases <bot>line in the PR description, which the squash commit carries ontomain(the repo's squash-message setting is alreadyPR_BODY). A newGatejob indeploy-production.ymlbinds the pushed commit to its merged PR and deploys a bot only if a reviewer other than the author approved the PR after the intent was added, judged at themerged_atsnapshot. Refused bots get aThis closes the same gap prime-monorepo's per-app release-branch design (its PR #4865) targeted, without release branches: CI is the deployer here, so the gate runs in front of the deploy on reviewed
mainworkflow code. Release branches would need a ruleset bypass actor, which cannot beGITHUB_TOKEN, so they need an App or deploy key; both were weighed and declined. Rationale:docs/decisions/TIB-2026-09-09-release-intent-gated-deploys.md.Changes
packages/ci-scripts(new, tsx, no build):Releases <bot>parser on prime's golden fixture (HTML comments stripped first), PR body edit-history replay (fails closed at GitHub's 100-revision retention cap), pure gate, andmanifest.jsonas the single list of CI-deployable bots (replaces six hardcoded enumerations). 59 unit tests.deploy-production.yml:Gate→Deploymatrix (job-named<bot>). Productionworkflow_dispatchremoved (would bypass the gate); thedeploy-productionconcurrency group removed (PR-number tags need no same-day counter; GitHub drops pending runs per group).deploy-bot.yml: takespackage/github_environment/release_pr/publish_imagefrom the manifest, absorbsdeploy-quoter-bot-production.yml(Railway CLI 5.30.4, 30-minute timeout), and chainsRelease(<bot>-<PR#>, only after a successful deploy, idempotent on rerun),Notify, andImage(Docker Hub, for bots the manifest markspublishImage). The image lives in the per-bot chain so a sibling bot's failed deploy cannot skip it.release-slack-notify.ymlgainsworkflow_call. It had never run: GitHub fires noreleaseevent for releases created withGITHUB_TOKEN.deploy-staging.yml: manifest-driven matrix.pr-release-label-sync.yml(new): derives cosmeticrelease-<bot>labels from the body and restores hand-removed ones; nothing reads them.publish-quoter-bot-npm.yml: job-name proof acceptsquoter-bot / Releaseand the oldRelease-quoter-botfor historical runs. npm versions still come frompackage.json..claude/commands/ci-*.md(stalepackages/{bot}paths and version-bump assumption).Findings recorded in the TIB
main, thoughchecks.yml,deploy-staging.yml, and TIB-2026-07-15 assume a queue.Independent review
Codex (gpt-5.6-sol, high) reviewed the implementation. Incorporated: hidden-HTML-comment intent, the 100-edit history cap, the aggregate-matrix dependency that could skip the quoter image, and label re-sync on
labeled/unlabeled. Deferred: same-bot rapid releases can be dropped or reordered by the per-bot concurrency group (pre-existing, not a regression) → BOTS-128.Verification
pnpm --filter @repo/ci-scripts run typecheck,pnpm -r run typecheck(0 errors after the contracts build),pnpm lint,pnpm format:check,pnpm knip: clean.pnpm vitest run: all pass except the fork/e2e suites that requireRPC_URL_8453(environmental).To verify on the first live run (cannot be tested locally)
quoter-bot / Release(the npm publisher's proof depends on it; the old name is still accepted).contents: write/id-token: writegrants are needed for the callee's skippedRelease/Imagejobs to validate; if GitHub accepts a narrower grant, tighten it.Rollout: no admin action needed. Environment branch policies stay
main. First release after merge: a PR whose body saysReleases <bot>, approved after that line is present.🤖 Generated with Claude Code