Skip to content

feat(release): continuous release on merge via the Oak Semantic Release Bot#44

Merged
jimCresswell merged 3 commits into
mainfrom
feat/continuous-release-on-merge
Jun 18, 2026
Merged

feat(release): continuous release on merge via the Oak Semantic Release Bot#44
jimCresswell merged 3 commits into
mainfrom
feat/continuous-release-on-merge

Conversation

@jimCresswell

Copy link
Copy Markdown
Contributor

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 (a main-ruleset bypass actor, app ID 2995796) bumps pyproject.toml + uv.lock + CHANGELOG.md, commits + tags vX.Y.Z, pushes straight to protected main, then uv builds and publishes a GitHub Release with the wheel + sdist.

Mechanics

  • Trigger: workflow_run after the CI workflow succeeds on main (so a release only cuts when green), plus workflow_dispatch for a manual major.
  • Auth: actions/create-github-app-token (secrets RELEASE_APP_ID / RELEASE_APP_PRIVATE_KEY); checkout keeps the bot token so git push to main bypasses the ruleset.
  • Loop guard: the bump commit carries [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.)
  • 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. The hook is SKIP-protected in the agent guardrail.
  • Bootstrap: a fresh fork whose committed version is untagged is released as-is (the adopter's chosen first version), not bumped past.

Review

config + security + code reviewers ran pre-merge. Adopted: bootstrap-as-is, uv lock for lock consistency, explicit publish gating, clean error on an unreadable commit-msg file, and completing the SKIP-reason text. Rejected with reasoning: pinning checkout to workflow_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 loosening BREAKING_MARKER (over-blocking is the safe direction for a guard).

Cutover

The stale #43 chore(release): v0.3.1 PR + release/next branch (from the old machinery) are retired alongside this. Merging this PR triggers the first continuous release, which will release the changes accumulated since v0.3.0.

audit_release_workflow now enforces the workflow_run trigger and the [skip ci] loop guard; README / dev-tooling / governance docs updated.

🤖 Generated with Claude Code

jimCresswell and others added 2 commits June 18, 2026 13:43
…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>
Copilot AI review requested due to automatic review settings June 18, 2026 12:56
@github-code-quality

github-code-quality Bot commented Jun 18, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Python

Python / code-coverage/pytest

The overall coverage remains at 89%, unchanged from the branch.


Updated June 18, 2026 13:06 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 protected main.
  • Add prevent-accidental-major commit-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 as workflow_run under on:. For a workflow_run trigger, branches must be nested under on.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-main CI 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>
@sonarqubecloud

Copy link
Copy Markdown

@jimCresswell jimCresswell merged commit bcef81c into main Jun 18, 2026
7 checks passed
@jimCresswell jimCresswell deleted the feat/continuous-release-on-merge branch June 18, 2026 13:07
jimCresswell added a commit that referenced this pull request Jun 18, 2026
…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>
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