Skip to content

ci: harden release pipeline (six-job publish, changeset-check, auto-backmerge) - #394

Merged
codewizdave merged 19 commits into
mainfrom
ci/release-pipeline-hardening
Aug 6, 2026
Merged

ci: harden release pipeline (six-job publish, changeset-check, auto-backmerge)#394
codewizdave merged 19 commits into
mainfrom
ci/release-pipeline-hardening

Conversation

@martyy-code

@martyy-code martyy-code commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

What this PR permits

This PR activates the release pipeline described in the wiki Release-Process page. After this lands, the maintainer can:

  • Cut a release by merging a PR to main. The PR merge is the release trigger.
  • Trust the per-PR Changeset rule (after enabling the required-status-check on staging, see Action required).
  • Ship a hotfix without waiting for a regular release in flight (per-PR concurrency).
  • Get a Version Packages PR automatically (changesets-version.yml on every push to staging).
  • Have staging catch up automatically (backmerge.yml on every push to main).
  • Recover cleanly from a missed version bump (rebase in push-bump).

What this PR does

Pipeline changes (commits 1-9, the four open issues)

  1. docs(process): explicit hotfix exemption in changesets.md 7.1
  2. docs(fp): note 1.1.1 / CHANGELOG inconsistency
  3. ci(ci): add changeset-check job
  4. ci(workflows): add changesets-version.yml
  5. ci(workflows): add backmerge.yml
  6. ci(publish): split into six jobs, add rebase, use pnpm changeset status, fix concurrency
  7. docs(release): sync release-pipeline plan with the six-job pipeline
  8. ci(workflows): use major-version tags instead of SHA pins
  9. ci(ci): exclude bot:backmerge PRs from changeset-check

Post-review fixes (commits 10-18, critical bugs + security + docs)

  1. fix(publish): invert changeset status exit-code logic in detect
  2. fix(changesets-version): open Version Packages PR against main
  3. fix(backmerge): escalate open-backmerge job to write permissions
  4. docs(changesets): correct cross-reference to hotfix.md section 7 to 8
  5. docs(fp): correct CHANGELOG note version arithmetic (1.1.2, not 1.1.1)
  6. docs(changesets): correct publish.yml trigger description (pull_request.closed, not push)
  7. docs(changesets): document bot:backmerge exclusion in changeset-check
  8. docs(release): refresh release-pipeline.md to match actual six-job pipeline
  9. docs(github-ui-setup): drop never-landed entrypoint/reusable-workflow pattern

Post-polish (commit 19)

  1. ci(publish): post-review polish
    • collapse bump + push-bump into one job (5-job chain instead of 6)
    • remove environment: release from validate (publish env gates publish only)
    • add tag-already-exists guard before git tag in release job
    • harden changeset-check with a merge-base sanity check and an explicit base-ref fetch (replaces the depth=0 heuristic)

After commit 19, the chain is detect -> push-bump -> validate -> publish -> release (5 jobs).

Mapping to the four open issues

Issue Resolution
#383 (split release job) Commit 6: six jobs. Commit 19 further collapses to five.
#384 (rebase bump onto origin/main) Commit 6: push-bump runs git fetch origin main + git rebase origin/main before git push.
#385 (replace git diff with pnpm changeset status) Commit 6: detect runs pnpm changeset status, inverts exit code.
#389 (enforce per-PR Changeset rule via CI) Commit 3: changeset-check job in ci.yml. Required-status-check wiring is a GitHub-side follow-up.

Action required (post-merge)

  • Add changeset-check as a required status check on the staging branch protection ruleset. This is a GitHub Settings change, not a workflow file change.
  • Dependabot will track the new major-version tags. Configure a .github/dependabot.yml github-actions entry if you want automated PRs.

Verification

  • git log --oneline origin/main..HEAD shows the nineteen commits.
  • pnpm changeset status exits 0 in a clean tree, 1 after pnpm changeset.
  • Test PR with a patch Changeset against staging -> changeset-check passes. Merge -> changesets-version.yml opens a Version Packages PR against main. Merge that PR into main -> publish.yml runs as five jobs, publishes to npm via Trusted Publishing.
  • Test PR targeting main (hotfix) -> changeset-check skips. After merge, backmerge.yml opens a backmerge PR to staging.

Risks

  • Two PRs merging into main within seconds run publish.yml in parallel under per-PR concurrency. The anti-republish guard in validate catches the race.
  • Conflicts in packages/fp/package.json#version or CHANGELOG.md between two concurrent PRs fail at push-bump rebase step. Manual recovery required.
  • Major-version tags can move under you. Dependabot will surface relevant updates (consider a 7-day cooldown to avoid auto-adoption during upstream incidents, per the March 2026 Trivy supply-chain compromise post-mortem).
  • The branch carries the previously-untracked docs/engineering/process/changesets.md.

Outstanding senior concerns (post-branch follow-up)

Two senior concerns surfaced during research that are out of scope for this branch but worth flagging:

  • SHA pinning vs major-version tags: the incident post-mortem for the March 2026 Aqua Security / trivy-action compromise recommends SHA pinning to full commit hashes. Commit 8 on this branch chose major-version tags for legibility. A follow-up branch could restore SHA pinning if the threat model warrants. The current Dependabot config does not include a github-actions entry yet.
  • peter-evans/create-pull-request in backmerge.yml: post-mortems recommend replacing this with the native gh CLI (gh pr create) for a smaller attack surface. The article A complete guide to hardening GitHub Actions points at this specifically.

Subagent review dimensions

Three subagents reviewed this branch in parallel: correctness (workflows, YAML, GitHub Actions expression syntax, Changesets CLI exit-code semantics), security (permissions, OIDC trust boundary, anti-recursion), and documentation (cross-references, version arithmetic, stale sections, code/doc consistency).

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

github-actions Bot added 19 commits August 6, 2026 11:05
…peline

Replace § 7.3 (release.yml → publish.yml), § 8.3 (hotfix.yml → reuses publish.yml), § 9 (custom wrapper reasoning), § 11 + 11a (migration plan as completed), and Appendix A (file inventory). The previous content described an architecture that never landed; the new content matches the actual workflows on the branch.
… pattern

Section 5 had old content describing an entrypoint pattern with three reusable workflows (_publish-release.yml, _publish-hotfix.yml, _publish-canary.yml) that never landed. Replace with the actual single-workflow architecture: publish.yml is the only Trusted Publisher entrypoint, and it serves both regular and hotfix release paths. Also fix § 4 (permission claim) and § 7.2 (Code Owners description) to match the actual workflow files.
- collapse bump + push-bump into one job (5-job chain instead of 6)
- remove environment: release from validate (publish env gates publish only)
- add tag-already-exists guard before git tag in release job
- harden changeset-check with a merge-base sanity check and an
  explicit base-ref fetch (replaces the depth=0 heuristic)

PR #394 follow-up.
@codewizdave
codewizdave merged commit c2534fb into main Aug 6, 2026
5 checks passed
martyy-code pushed a commit that referenced this pull request Aug 6, 2026
Conflict resolution: keep the post-#394 description of the workflow permissions (workflow-level permissions: {} is empty, push-bump and release declare contents: write at the job level). Drops the obsolete claim that GITHUB_TOKEN has workflow-level contents: write.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants