Skip to content

fix(release): harden release agent, guard PR body, and add missing test coverage#1018

Merged
ashleyshaw merged 2 commits into
developfrom
fix/release-governance-audit
Jun 19, 2026
Merged

fix(release): harden release agent, guard PR body, and add missing test coverage#1018
ashleyshaw merged 2 commits into
developfrom
fix/release-governance-audit

Conversation

@ashleyshaw

@ashleyshaw ashleyshaw commented Jun 19, 2026

Copy link
Copy Markdown
Member

Linked issues

Closes #968 (addresses release checklist confusion and governance gaps)

Summary

Deep audit of the release agent, workflows, and related documentation found four bugs and significant gaps in test coverage. This PR fixes all of them.

Bugs fixed

release.agent.js

  • Regex escape (getMergedPRs): \\d+ in a regex literal matched a literal backslash, so PR numbers were never extracted from git log output — contributor lists and PR-linked release notes were silently empty on every run.
  • createReleasePR body: both the shell and MCP providers generated a plain prose body. validate-main-branch-pr.cjs (the main-branch-guard) requires three specific sections — ## Linked issues & merged PRs, ## Changelog, and ### Checklist (Global DoD / PR) — so every automated release PR was blocked by the guard. Added buildReleasePRBody() helper used by both providers.
  • createReleasePR shell injection: backticks in the PR body were interpreted as command substitution when interpolated into the shell command. Now writes the body to a temp file and uses --body-file instead.
  • Husky v9 compatibility: npx husky run pre-commit is Husky v8 syntax. v9 is installed; the correct call is npx lint-staged directly.
  • Hardcoded /tmp/ path: release-notes temp file now uses os.tmpdir() for portability.
  • Stale comment: inline comment still referred to Husky pre-commit hooks after the lint-staged fix.

Test coverage added / rewritten

File Before After
release.agent.test.js Re-implemented logic inline; never imported the module Rewrites to use subprocess ESM pattern — tests determineNextVersion, compareVersions, isValidGitRef, buildReleasePRBody, detectBreakingChanges, generateHighlights, updateChangelog, validatePostReleaseChangelog, getReleaseProvider
changelogUtils.test.js New file Full coverage: parseChangelog, validateChangelog, getLatestRelease, getUnreleasedChanges, hasUnreleasedChanges
validate-changelog.test.js Single stub (file-exists check) CLI exit codes, success/failure output, section extraction, all 8 allowed section names
validate-main-branch-pr.test.js New file normaliseBranchName, extractReleaseVersion, isReleaseBranch, isHotfixBranch, isAllowedBranch, validatePullRequestMetadata (release and hotfix); verifies PR body shape satisfies guard

Issue template clarification (18-release.md)

The release issue checklist now explicitly states:

The release flow is: develop → release/vX.Y.Z → main. All feature/fix/chore branches merge to develop. The release branch is created FROM develop and its PR targets main.

This directly resolves the confusion in issue #968 where release/v0.6.0 PR opened against main looked wrong — the branch is created from develop, but the PR correctly targets main (only release/* and hotfix/* branches may merge to main, enforced by main-branch-guard).

Changelog

Fixed

  • release.agent.js: regex \\d+\d+ in getMergedPRs — PR numbers now correctly extracted
  • release.agent.js: automated release PR body now includes all three sections required by main-branch-guard
  • release.agent.js: automated release PR body now uses --body-file to avoid shell injection from backticks
  • release.agent.js: Husky v9 command corrected from npx husky run pre-commit to npx lint-staged
  • release.agent.js: temp file path uses os.tmpdir() instead of hardcoded /tmp/
  • 18-release.md: removed description field (issue templates must use about)

Added

  • changelogUtils.test.js: full test suite for core changelog parsing and validation utility
  • validate-main-branch-pr.test.js: full test suite for the main-branch-guard validation logic
  • release.agent.test.js: rewritten with real module imports via ESM subprocess pattern
  • validate-changelog.test.js: replaced stub with real CLI and integration tests

Checklist (Global DoD / PR)

  • All bugs identified in audit are fixed
  • Shell injection risk in createReleasePR fixed (uses --body-file)
  • All 72 test suites pass (668 tests)
  • lint:js exits 0 (no new errors)
  • Issue template updated to make develop → release/vX.Y.Z → main flow explicit
  • Rebased onto latest develop
  • CI green (pending this push)
  • Reviewed by maintainer (pending)

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@ashleyshaw, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 58 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 99bcd8cf-9194-4146-97c1-bca6ce6cae89

📥 Commits

Reviewing files that changed from the base of the PR and between 5916712 and 3013536.

📒 Files selected for processing (7)
  • .github/ISSUE_TEMPLATE/18-release.md
  • CHANGELOG.md
  • scripts/agents/__tests__/release.agent.test.js
  • scripts/agents/includes/__tests__/changelogUtils.test.js
  • scripts/agents/release.agent.js
  • scripts/validation/__tests__/validate-changelog.test.js
  • scripts/workflows/branch-policy/__tests__/validate-main-branch-pr.test.js
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-governance-audit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

✅ Template check passed after update. Thanks for fixing the PR description.

@github-actions

Copy link
Copy Markdown
Contributor

🎨 Mermaid Diagram Validation

✅ All Mermaid diagram checks passed.

Check Result
✅ Syntax (diagram type, direction, bracket matching) Passed
✅ Accessibility (accTitle / accDescr present) Passed
✅ Colour contrast (WCAG 2.2 AA ≥ 4.5:1) Passed

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

🔍 Reviewer Summary for PR #1018

CI Status:success
Files changed: 7
Risk Distribution: 0 critical, 1 high, 4 medium, 2 low

Recommendations

  • Ready to proceed pending human review

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Metadata governance

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request updates the release management workflow by enhancing the release issue template, introducing comprehensive Jest test suites for changelog utilities, main branch PR validation, and the release agent, and updating the release agent script to use lint-staged instead of Husky. It also adds a structured release PR body generator to satisfy branch guard policies. Feedback on these changes highlights a potential shell injection and syntax error risk in createReleasePR due to shell command interpolation of backticks, recommending the use of a temporary body file with the gh CLI instead. Additionally, a minor documentation update is suggested to keep a code comment in sync with the transition to lint-staged.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

const title = `chore(release): v${version}`;
const body =
"Automated release PR generated by release.agent.js. Includes version bump, changelog update, and tag creation.";
const body = buildReleasePRBody(version);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Shell Injection & Syntax Error Risk

The body string returned by buildReleasePRBody(version) contains literal backticks (e.g., \\CHANGELOG.md\``) and newlines. When this is interpolated directly into the shell command inside double quotes:

exec(
  `gh pr create --base main --head ${branch} --title "${title}" --body "${body}"`,
  dryRun,
);

the shell (used by execSync under the hood) will interpret the backticks inside the double quotes as command substitution. This will cause the shell to attempt to execute CHANGELOG.md (and other backticked terms) as commands, leading to errors like CHANGELOG.md: command not found. Additionally, literal newlines and any double quotes within the body will break the shell command syntax.

Recommended Solution

Instead of passing the multi-line markdown body via the --body argument, write the PR body to a temporary file and use the --body-file (or -F) option of the gh CLI, similar to how release notes are handled in createRelease.

Here is how the entire createReleasePR function should be refactored:

function createReleasePR(version, branch, options = {}) {
  const { dryRun = false } = options;
  const title = `chore(release): v${version}`;
  const body = buildReleasePRBody(version);

  if (dryRun) {
    console.log(
      `[DRY-RUN] Would create PR from ${branch} to main with title "${title}"`,
    );
    return;
  }

  const bodyFile = path.join(os.tmpdir(), `release-pr-body-${version}.md`);
  fs.writeFileSync(bodyFile, body, "utf8");

  try {
    exec(
      `gh pr create --base main --head ${branch} --title "${title}" --body-file "${bodyFile}"`,
      dryRun,
    );
    console.log("✓ Release PR created");
  } finally {
    if (fs.existsSync(bodyFile)) {
      fs.unlinkSync(bodyFile);
    }
  }
}

Comment thread scripts/agents/release.agent.js Outdated
@@ -1158,7 +1183,7 @@ async function run() {
// Step 5: Stage all changes and run Husky pre-commit hooks, then commit

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The comment still mentions running Husky pre-commit hooks, but the code has been updated to run lint-staged directly. Let's update the comment to keep it accurate and in sync with the implementation.

Suggested change
// Step 5: Stage all changes and run Husky pre-commit hooks, then commit
// Step 5: Stage all changes and run lint-staged, then commit

…st coverage

Bugs fixed in release.agent.js:
- Regex escape (getMergedPRs): \d+ in regex literal matched literal
  backslash; PR numbers were silently empty on every run.
- createReleasePR body: both shell and MCP providers generated a plain
  prose body. main-branch-guard requires three specific sections, so
  every automated release PR was blocked. Added buildReleasePRBody()
  helper used by both providers.
- createReleasePR shell injection: backticks in the PR body caused
  command substitution when interpolated into the shell command. Now
  writes body to a temp file and uses --body-file instead.
- Husky v9 compatibility: npx husky run pre-commit is v8 syntax. Correct
  call for v9 is npx lint-staged directly.
- Hardcoded /tmp/ path: release-notes temp file now uses os.tmpdir().
- Stale comment: inline comment still referred to Husky after fix.

Test coverage added / rewritten:
- release.agent.test.js: rewrites to use subprocess ESM pattern
- changelogUtils.test.js: new file, full coverage of parsing/validation
- validate-changelog.test.js: replaced stub with real CLI and integration tests
- validate-main-branch-pr.test.js: new file, full coverage of guard logic

Issue template clarification (18-release.md):
- Adds explicit develop → release/vX.Y.Z → main flow comment
- Checklist now specifies that release branch is created FROM develop
  and the PR targets main

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LNVr8xNwtAwfWVpaxW75Aq
@ashleyshaw ashleyshaw force-pushed the fix/release-governance-audit branch from 996d90e to 3ac2074 Compare June 19, 2026 11:10
@ashleyshaw ashleyshaw marked this pull request as ready for review June 19, 2026 11:37
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@ashleyshaw ashleyshaw enabled auto-merge (squash) June 19, 2026 11:37
@ashleyshaw

Copy link
Copy Markdown
Member Author

@Mergifyio queue

@mergify

mergify Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

  • 🟠 Waiting for queue conditions
  • ⏳ Enter queue
  • ⏳ Run checks
  • ⏳ Merge
Waiting for
  • -closed [📌 queue requirement]
  • any of: [🔀 queue conditions]
    • all of: [📌 queue conditions of queue rule Merge queue]
      • any of: [🛡 GitHub branch protection]
        • check-neutral = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-skipped = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-success = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
      • any of: [🛡 GitHub branch protection]
        • check-neutral = Validate PR Template / validate-pr-template
        • check-skipped = Validate PR Template / validate-pr-template
        • check-success = Validate PR Template / validate-pr-template
    • all of: [📌 queue conditions of queue rule dependabot]
      • label=area:dependencies
      • any of: [🛡 GitHub branch protection]
        • check-neutral = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-skipped = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-success = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
      • any of: [🛡 GitHub branch protection]
        • check-neutral = Validate PR Template / validate-pr-template
        • check-skipped = Validate PR Template / validate-pr-template
        • check-success = Validate PR Template / validate-pr-template
All conditions
  • -closed [📌 queue requirement]
  • any of [🔀 queue conditions]:
    • all of [📌 queue conditions of queue rule Merge queue]:
      • any of [🛡 GitHub branch protection]:
        • check-neutral = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-skipped = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-success = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
      • any of [🛡 GitHub branch protection]:
        • check-neutral = Validate PR Template / validate-pr-template
        • check-skipped = Validate PR Template / validate-pr-template
        • check-success = Validate PR Template / validate-pr-template
    • all of [📌 queue conditions of queue rule dependabot]:
      • author~=^(dependabot\[bot\]|app/dependabot)$
      • label=area:dependencies
      • any of [🛡 GitHub branch protection]:
        • check-neutral = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-skipped = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
        • check-success = CI • Unified Checks (Lint, Test, Validate) / All Checks Passed
      • any of [🛡 GitHub branch protection]:
        • check-neutral = Validate PR Template / validate-pr-template
        • check-skipped = Validate PR Template / validate-pr-template
        • check-success = Validate PR Template / validate-pr-template
      • -conflict
      • -draft
      • base=develop
  • -conflict [📌 queue requirement]
  • -draft [📌 queue requirement]
  • any of [📌 queue -> configuration change requirements]:
    • -mergify-configuration-changed
    • check-success = Configuration changed
  • any of [📌 queue requirement]:
    • check-neutral = Mergify Merge Protections
    • check-skipped = Mergify Merge Protections
    • check-success = Mergify Merge Protections

@github-actions github-actions Bot added status:needs-review Awaiting code review type:bug Bug or defect priority:normal Default priority area:documentation Docs & guides area:tests Test suites & harnesses area:scripts Scripts & tooling lang:js JavaScript/TypeScript lang:md Markdown content/docs type:chore Chore / small hygiene change meta:needs-changelog Requires a changelog entry before merge labels Jun 19, 2026
@ashleyshaw ashleyshaw merged commit 184ff65 into develop Jun 19, 2026
36 checks passed
@ashleyshaw ashleyshaw deleted the fix/release-governance-audit branch June 19, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:documentation Docs & guides area:scripts Scripts & tooling area:tests Test suites & harnesses lang:js JavaScript/TypeScript lang:md Markdown content/docs meta:needs-changelog Requires a changelog entry before merge priority:normal Default priority status:needs-review Awaiting code review type:bug Bug or defect type:chore Chore / small hygiene change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release v0.6.0 — Community Health, Governance Docs, and Meta Agent Foundations

2 participants