Skip to content

chore(tooling): cut releases through a pull request, tag after the merge - #65

Merged
Alex1990 merged 1 commit into
mainfrom
chore/release-pr-flow
Sep 18, 2026
Merged

Alex1990 merged 1 commit into
mainfrom
chore/release-pr-flow

Conversation

@Alex1990

Copy link
Copy Markdown
Owner

Problem

A release is a change to main, and main only moves through a pull request: the Main branch ruleset refuses a direct push with 422 Changes must be made through a pull request, for every credential, the owner's included. What the owner's bypass (bypass_mode: pull_request) buys is merging a PR without a second reviewer — which is what makes a solo release possible — not pushing one.

bumpp's default flow pushed the release commit and its tag straight to main, so the next release would have failed at the push. The previous release (v1.1.0, 2026-09-04) predates the gate: it was cut on 2026-09-04, before the ruleset became the boundary on 2026-09-15.

Changes

  • bump.config.ts — releases run in bumpp's pr mode: commit on release/v{version}, push that branch, open the PR. pr.base is pinned to main because bumpp otherwise derives the base from origin/HEAD, which still said master in a clone made before the rename; with the wrong base its precondition check refuses to run at all.
  • package.json — pnpm release passes -a --commit "chore(release): {version}" on the command line; new pnpm release:tag.
  • scripts/release-tag.mjs — tags the merged release commit on main. bumpp's PR mode creates no tag by design: it cannot know the merge commit, and a squash or rebase merge rewrites the release commit, so a tag made on the branch would point at a commit that never lands on main. The script refuses a dirty tree, a branch other than main, a main out of sync with origin/main, and an existing tag; --no-push rehearses.
  • docs/agents/release.md — rewritten around the PR flow, including the gotcha below. Norms layer, hence the PR.

Why the commit message left the config file: bumpp's CLI defaults (--commit, --all) win over the config's object form, so commit: { message } in bump.config.ts is silently replaced by the default chore: release v<version> — which is exactly how v1.1.0 was committed, config and all. -a is passed for the same reason.

Verification

Full rehearsal against the real repository, then reverted. pnpm release --release patch --yes on main (with this branch merged locally) created release/v1.1.1, committed chore(release): 1.1.1 with the regenerated CHANGELOG.md, pushed the branch and opened #64 — base main, title chore(release): 1.1.1, body 1.1.0 → 1.1.1, and no tag, as designed. #64 and its branch were then closed and deleted, and main reset to origin/main.

scripts/release-tag.mjs exercised in a throwaway clone with a local bare remote (no GitHub writes):

scenario result
clean main, in sync, tag absent exit 0 — annotated v1.1.0 created and pushed
re-run exit 1 — tag v1.1.0 already exists
branch other exit 1 — on "other", expected "main"
untracked file present exit 1 — the working tree is not clean
local main ahead of its remote exit 1 — local main is not in sync with origin/main

Gates on the rebased branch (rebased onto the merged #63): pnpm lint → 0 warnings / 0 errors, pnpm check:types clean, pnpm fmt:check clean.

The `Main branch` ruleset refuses every direct push to `main` — including the
release commit — with `422 Changes must be made through a pull request`, and
the owner's bypass (`bypass_mode: pull_request`) covers merging a PR, not
pushing one. `bumpp`'s default flow pushed the release commit and its tag
straight to `main`, so the next release would have failed at the push.

Releases now use bumpp's `--pr` mode: commit on `release/v<version>`, push that
branch, open the PR. `pr.base` is pinned to `main` because bumpp otherwise
derives the base from `origin/HEAD`, which still said `master` in this clone —
with the wrong base, its precondition check refuses to run at all.

The tag moves to `pnpm release:tag` (`scripts/release-tag.mjs`), run on the
updated `main` after the merge. bumpp's PR mode creates no tag by design: it
cannot know the merge commit, and a squash or rebase merge rewrites the release
commit, so a tag made on the branch would point at a commit that never lands on
`main`. The script refuses a dirty tree, a branch other than `main`, a `main`
out of sync with `origin/main`, and an existing tag; `--no-push` rehearses.

`package.json` passes `-a --commit "chore(release): {version}"` on the command
line rather than relying on the config file: bumpp's CLI defaults win over the
config's object form, which is why v1.1.0 was committed as
`chore: release v1.1.0` despite `commit: { message }` in `bump.config.ts`.

`docs/agents/release.md` is rewritten to match — it documented the direct push
that the gate now refuses.
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Metric % Covered / Total
Lines 91.73% 1121/1222
Statements 90.42% 1209/1337
Functions 92.76% 218/235
Branches 76.97% 575/747

@github-actions github-actions Bot added the needs-triage Maintainer needs to evaluate (loop can't decide; too risky; external PR) label Sep 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Loop triage — needs-triage (human-owned; the loop must not decide this one).

This is an owner-authored, same-repo PR that changes the norms layer (docs/agents/release.md) plus release tooling (bump.config.ts, package.json, new scripts/release-tag.mjs). AGENTS.md makes norms-layer changes proposals a human merges, and docs/agents/ops.md / docs/agents/release.md keep releases (pnpm release, pnpm release:tag, pnpm publish) human-only. The change is already written and checks out, so no agent-actionable work remains — only your review/merge decision. Routing to the human inbox.

Verified read-only — the change checks out

  • pnpm lint → 0 warnings / 0 errors; pnpm fmt:check clean; pnpm build + pnpm check:types exit 0. CI on head 2e40820: Lint pass, Test pass; base a3384f77 == origin/main, one commit.
  • The diagnosis is confirmed against the installed bumpp 12.2.2: normalizeOptions() accepts commit only as a string/boolean, so the old commit: { message } object fell through to the default message: "chore: release v" — the CLI --commit "chore(release): {version}" is the fix. The pr object in bump.config.ts enables PR mode on its own (no --pr flag needed) and PR mode drops the tag with a warning, so the separate release:tag step is required. pr.base: 'main' is honoured.
  • scripts/release-tag.mjs reproduced in a throwaway clone with a local bare remote (no GitHub writes): clean main in sync → annotated v1.1.0 created and pushed; re-run → tag v1.1.0 already exists; branch other → on "other", expected "main"; untracked file → the working tree is not clean; local main ahead → local main is not in sync with origin/main. The sync check is strict equality, so a behind remote is caught too.
  • Diff is confined to bump.config.ts, docs/agents/release.md, package.json, scripts/release-tag.mjs; no .github/workflows/**.

No ready-for-agent work exists. Nothing pushed or written by the loop.

@Alex1990
Alex1990 merged commit a313e70 into main Sep 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-triage Maintainer needs to evaluate (loop can't decide; too risky; external PR)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant