feat(release): continuous release on merge via the Oak Semantic Release Bot#44
Conversation
…se Bot Replace the release-PR pattern with continuous semantic-release. After CI passes on main, the Release workflow computes the Conventional Commits increment and the Oak Semantic Release Bot (a main-ruleset bypass actor) bumps pyproject + uv.lock + CHANGELOG, commits + tags, and pushes straight to protected main, then builds and publishes the GitHub Release with the wheel + sdist. So every qualifying merge advances the version, which is what the owner asked for. Mechanics: - trigger on workflow_run after CI success on main (release only when green), plus workflow_dispatch for a manual major; - authenticate with actions/create-github-app-token (RELEASE_APP_ID / RELEASE_APP_PRIVATE_KEY), checkout with that token so the push bypasses the ruleset; - the bump commit carries [skip ci] so it does not re-trigger CI -> Release (an infinite loop). A secondary guard: a re-run finds no commits since the fresh tag and stands down. Majors stay manual (owner decision): a breaking marker makes the auto-release stand down, and the new prevent-accidental-major commit-msg hook (tools/prevent_accidental_major.py, reusing release_increment.is_breaking) rejects the marker at commit time so it cannot land by accident and silently halt releases. The hook is protected from SKIP in the agent guardrail policy. audit_release_workflow now requires the workflow_run trigger and the [skip ci] loop guard; docs (README, dev-tooling, governance) updated, and the stale coverage-doc figure corrected to the branch-aware floor of 86. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopted the substantive pre-merge review findings: - bootstrap (a fresh fork whose committed version is untagged) now releases that version as-is instead of forcing a PATCH bump, so an adopter's first version is the one they chose; - run `uv lock` after the bump so uv.lock's project version always matches pyproject, even if a future uv stops updating it during `cz bump`; - the publish step now gates on the release step having set its version output, so it can never run against a half-applied release; - prevent_accidental_major handles an unreadable commit-message file with a clean error instead of a traceback, with a test for the missing-file path; - the blocked-pre-commit-skip reason now names prevent-accidental-major (kept in lockstep across policy.json, repo_audit, and the hooks test). Rejected, with reasoning recorded: pinning checkout to workflow_run.head_sha (would make the bump push non-fast-forward whenever a second merge landed — incompatible with direct-push; releasing main's protected tip is intended), explicit token repo-scoping (create-github-app-token already defaults to the current repo), and loosening BREAKING_MARKER (over-blocking is the safe direction for a guard). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Code Coverage OverviewLanguages: Python Python / code-coverage/pytestThe overall coverage remains at 89%, unchanged from the branch. Updated |
There was a problem hiding this comment.
Pull request overview
This PR replaces the repository’s release-PR workflow with a continuous-release-on-merge model: after CI succeeds on main, a GitHub App (“Oak Semantic Release Bot”) bumps versioned files, commits + tags directly to protected main, and publishes a GitHub Release. It also adds a commit-msg hook to prevent accidentally introducing breaking-change markers that would halt auto-releases and enforces the new release workflow invariants via repo_audit.
Changes:
- Switch Release automation to
workflow_run(post-CI) +workflow_dispatch(manual major), using a GitHub App token to push bump commits/tags to protectedmain. - Add
prevent-accidental-majorcommit-msg hook + tests; update agent policy/audit to block skipping it. - Update docs and repo-audit tests to reflect continuous releases and the
[skip ci]loop guard.
Blocking review notes:
.github/workflows/release.yml:branches: [main]is currently indented at the same level asworkflow_rununderon:. For aworkflow_runtrigger,branchesmust be nested underon.workflow_run. As written, the workflow trigger configuration is invalid or the branch filter won’t apply, which can break releases or run them for non-mainCI runs. This must be fixed before merge.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tools/repo_audit.py |
Updates repo-audit to enforce the new Release workflow trigger (workflow_run) and require a [skip ci] loop guard; adds the new hook ID to blocked SKIP list. |
tools/prevent_accidental_major.py |
Adds a commit-msg hook that rejects breaking-change markers using the shared release_increment.is_breaking logic. |
tests/test_repo_audit.py |
Updates Release workflow audit tests for workflow_run triggering and adds a test for the [skip ci] requirement. |
tests/test_prevent_accidental_major.py |
Adds unit tests covering allowed messages, rejection of breaking markers, and error handling. |
tests/test_agent_hooks.py |
Updates expected blocked pre-commit SKIP IDs/reason to include prevent-accidental-major. |
README.md |
Updates release documentation to continuous-release-on-merge and documents manual major policy + hook. |
docs/repository-governance.md |
Updates governance guidance to reflect the release bot + required secrets/bypass actor wiring. |
docs/dev-tooling.md |
Updates dev tooling docs for continuous releases, branch coverage wording, and the new hook/loop guard. |
.pre-commit-config.yaml |
Adds the prevent-accidental-major commit-msg hook entry. |
.gitignore |
Ignores .sonarlint/ and .vscode/. |
.github/workflows/release.yml |
Replaces release-PR pattern with continuous release on CI success; introduces App token auth, bump+tag push, and publish step. |
.agent/hooks/policy.json |
Adds prevent-accidental-major to blocked SKIP hook IDs and updates the SKIP bypass reason message. |
SonarCloud flagged prevent_accidental_major reading a path straight from argv as path injection (pythonsecurity:S8707). The impact is negligible for a commit-msg hook (git supplies the path; the tool emits one bit), but fix-at-source beats suppression: canonicalise the path and confine it to the worktree-aware git directory before reading, so a stray argument cannot make the hook read an arbitrary file. An allowed_base seam keeps it testable without polluting .git, with tests for the traversal refusal and the git-directory resolution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
…tcha (#45) Closeout for PR #44 (continuous release on merge). Refresh continuity, the program memory, and the napkin to reflect that the release-PR pattern is retired in favour of the Oak Semantic Release Bot pushing the bump to protected main, and document the gotcha that bit #44's own merge: a feature commit or PR message that quotes the CI-skip marker in prose makes the squash-merge skip CI, so no release fires. Adds a dev-tooling note so template adopters avoid it. Merging this (with a clean message) is the first clean push to main since #44, so it should trigger the first continuous release (the computed minor, v0.4.0) and verify the bot's push to protected main. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>



What & why
Replaces the release-PR pattern with continuous release on merge, so the version advances on every qualifying merge to
main(owner directive).After CI passes on
main, the Release workflow computes the Conventional Commits increment and the Oak Semantic Release Bot (amain-ruleset bypass actor, app ID 2995796) bumpspyproject.toml+uv.lock+CHANGELOG.md, commits + tagsvX.Y.Z, pushes straight to protectedmain, thenuv builds and publishes a GitHub Release with the wheel + sdist.Mechanics
workflow_runafter theCIworkflow succeeds onmain(so a release only cuts when green), plusworkflow_dispatchfor a manual major.actions/create-github-app-token(secretsRELEASE_APP_ID/RELEASE_APP_PRIVATE_KEY); checkout keeps the bot token sogit pushtomainbypasses the ruleset.[skip ci], so its push doesn't re-trigger CI → Release. (Secondary guard: a re-run finds nothing since the fresh tag and stands down.)prevent-accidental-majorcommit-msg hook (tools/prevent_accidental_major.py, reusingrelease_increment.is_breaking) rejects the marker at commit time. The hook isSKIP-protected in the agent guardrail.Review
config + security + code reviewers ran pre-merge. Adopted: bootstrap-as-is,
uv lockfor lock consistency, explicit publish gating, clean error on an unreadable commit-msg file, and completing the SKIP-reason text. Rejected with reasoning: pinning checkout toworkflow_run.head_sha(would make the bump push non-fast-forward under concurrent merges — incompatible with direct-push), explicit token repo-scoping (the action already defaults to current-repo scope), and looseningBREAKING_MARKER(over-blocking is the safe direction for a guard).Cutover
The stale
#43 chore(release): v0.3.1PR +release/nextbranch (from the old machinery) are retired alongside this. Merging this PR triggers the first continuous release, which will release the changes accumulated sincev0.3.0.audit_release_workflownow enforces theworkflow_runtrigger and the[skip ci]loop guard; README / dev-tooling / governance docs updated.🤖 Generated with Claude Code