Skip to content

feat: add discord automated release notifications#564

Merged
zachyale merged 2 commits into
developfrom
feat/add-automated-gh-release-notifications
Apr 17, 2026
Merged

feat: add discord automated release notifications#564
zachyale merged 2 commits into
developfrom
feat/add-automated-gh-release-notifications

Conversation

@zachyale

@zachyale zachyale commented Apr 17, 2026

Copy link
Copy Markdown
Member

Description

Add a unified release notification flows for stable releases, nightly builds, & manually triggered release candidate comms.

This introduced Discord notifications for a consistent message + embed format, generates full changelogs from explicit git ranges, and links Discord users to the changelog in GitHub.

Nightly notifications now compare the current develop head against the previous successful scheduled nightly run, release candidate notifications compare the selected candidate against the latest stable tag, and stable notifications link directly to the GitHub release notes.

Changes

  • add a shared range-based changelog generator as a local composite action
  • post Discord notifications with a flow specific messages within a View Changelog embed
  • use GitHub release pages for stable changelog links + and workflow run summaries for nightly/RC changelog links
  • add a manual nightly smoke-test path that can skip image publishing while still generating notes and sending Discord notifications
  • nightly changelog ranges compare against the previous successful scheduled nightly run
  • rc changelogs compare the head of develop against main
  • stable reuses this format for discord but continues to use the existing release changelog structure

Summary by CodeRabbit

  • New Features

    • Automated release-notes generation for ref ranges producing markdown, JSON, and CI step outputs; artifacts uploaded for inspection.
    • Reusable workflows to post formatted Discord notifications for nightlies, release candidates, and stable releases with changelogs, links, and action buttons.
    • Nightly and candidate pipelines now emit changelog/version metadata for downstream jobs.
  • Chores

    • Added release tooling, standardized release-policy mapping and scripts, and updated release-related build scripts and dev dependencies.

@coderabbitai

coderabbitai Bot commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 738d4f01-cab4-4ed0-a837-1660a13c4013

📥 Commits

Reviewing files that changed from the base of the PR and between 9f20cf5 and 50bf8c4.

⛔ Files ignored due to path filters (1)
  • tools/release/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • .github/actions/compute-release-notes/action.yml
  • .github/workflows/notify-discord-release-notes.yml
  • .github/workflows/publish-nightly.yml
  • .github/workflows/release-candidate.yml
  • .github/workflows/release-main.yml
  • tools/release/Justfile
  • tools/release/package.json
  • tools/release/release-policy.cjs
  • tools/release/release.config.cjs
  • tools/release/scripts/generate-range-notes.mjs

📝 Walkthrough

Walkthrough

Adds a composite GitHub Action to compute range-based release notes, a reusable Discord notification workflow, updates nightly/release workflows to generate and publish range notes, and introduces Node.js/Just tooling, policy config, and a generator script to produce JSON/Markdown release notes and predicted versions.

Changes

Cohort / File(s) Summary
Compute Release Notes Action
.github/actions/compute-release-notes/action.yml
New composite action: checks out to_ref (full history), installs just and Node.js, runs just release range-notes, parses tools/release/range-release.json with jq, sets multiple outputs and GITHUB_STEP_SUMMARY, and uploads artifacts.
Discord Notification Workflow
.github/workflows/notify-discord-release-notes.yml
New reusable workflow_call workflow that composes Discord embed (channel, versions, compare/details URLs, mention_text), builds conditional buttons, and posts payload to a provided discord_webhook.
Publish Nightly Workflow
.github/workflows/publish-nightly.yml
Modified: added publish_image dispatch input; resolve_ref fetches full history and computes base_tag, from_ref, nightly_tag; added generate-notes job (uses compute-release-notes action) and notify-discord reusable workflow; added/changed job outputs including image_tag and range-note outputs.
Release Candidate Workflow
.github/workflows/release-candidate.yml
New workflow: resolve_ref validates ancestry and computes candidate_sha, base_tag, current_version; generate-notes runs local compute-release-notes action; notify-discord calls Discord workflow for rc channel with appropriate webhook selection and mentions.
Main Release Workflow
.github/workflows/release-main.yml
Added prepare-release-notification job to fetch release body/tag/compare info and export base_tag, compare_url, release_url, release_version; added notify-discord job calling the Discord workflow with channel: stable.
Release Tooling & Config
tools/release/Justfile, tools/release/package.json, tools/release/release-policy.cjs, tools/release/release.config.cjs
Added range-notes Just recipe and release:range-notes npm script; added release-policy.cjs (types, noteKeywords, writerSortFields); updated release.config.cjs to import/use policy and derive releaseRules/writerSortFields; added semantic-release plugins to devDeps.
Range Notes Generator Script
tools/release/scripts/generate-range-notes.mjs
New Node.js script: reads env inputs (FROM_REF/TO_REF, base tag/version, repo URL), resolves SHAs, parses conventional-commit messages against release policy, groups commits, extracts breaking notes, computes overall releaseType and predicted next version, writes range-release.json and range-release-notes.md.

Sequence Diagram

sequenceDiagram
    participant Trigger as GitHub Event
    participant Workflow as Workflow
    participant Resolver as resolve_ref Job
    participant Generator as generate-notes Job
    participant Action as compute-release-notes Action
    participant Tooling as Release Tooling (just / Node)
    participant Notifier as notify-discord Job
    participant Discord as Discord Webhook

    Trigger->>Workflow: trigger (schedule/dispatch/dispatch)
    Workflow->>Resolver: run resolve_ref (checkout, compute tags/refs)
    Resolver-->>Workflow: export refs/outputs
    Workflow->>Generator: run generate-notes with refs
    Generator->>Action: invoke composite action
    Action->>Tooling: install & run just release range-notes
    Tooling-->>Action: produce JSON/MD/log
    Action-->>Generator: set outputs (compare_url, predicted_version, release_notes, ...)
    Workflow->>Notifier: call notify-discord with metadata
    Notifier->>Discord: POST webhook (embed + buttons)
    Discord-->>Notifier: response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • imajes
  • balazs-szucs

Poem

🐰 I nibble commits from FROM to TO,

I sort the feats and fixes neat in rows,
I guess the version with a twitch and pose,
I pack the notes and send them where Discord glows,
Hooray — the pipeline hops and goes! 🥕🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows conventional commit format with 'feat' prefix and clearly describes the main change of adding Discord automated release notifications.
Description check ✅ Passed The PR description is comprehensive and complete, covering the objectives, changes, and implementation details across all three notification flows (stable, nightly, RC).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-automated-gh-release-notifications
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/add-automated-gh-release-notifications

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.

@coderabbitai coderabbitai Bot requested a review from balazs-szucs April 17, 2026 22:52
@coderabbitai coderabbitai Bot added the feature label Apr 17, 2026

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🧹 Nitpick comments (6)
tools/release/scripts/generate-range-notes.mjs (3)

178-205: detectReleaseType duplicates/drifts from release-policy.cjs.

The release impact for feat/fix/perf/revert/refactor is hardcoded here rather than derived from policyByType.get(type).release. This diverges from the single source of truth: e.g., feat is implicitly minor here but has no release field in the policy; fix/perf are hardcoded patch with no policy entry; and if someone later sets release: "minor" for, say, perf in release-policy.cjs, the semantic-release flow picks it up but this range-notes generator does not — producing inconsistent predicted versions between stable and RC/nightly flows.

Consider deriving directly from the policy, with feat as the only special-case (conventionalcommits default → minor) when no explicit release is configured:

♻️ Proposed refactor
 function detectReleaseType(conventional, body) {
   if (!conventional) {
     return "none";
   }

   if (conventional.breaking || findBreakingNotes(body, conventional.subject).length > 0) {
     return "major";
   }

-  if (conventional.type === "feat") {
-    return "minor";
-  }
-
-  if (conventional.type === "fix" || conventional.type === "perf" || conventional.type === "revert") {
-    return "patch";
-  }
-
-  if (conventional.type === "refactor") {
-    return "patch";
-  }
-
   const policy = policyByType.get(conventional.type);
-  if (policy?.release === false) {
-    return "none";
-  }
-
-  return "none";
+  if (policy && typeof policy.release === "string") {
+    return policy.release;
+  }
+  if (policy?.release === false) {
+    return "none";
+  }
+  // conventionalcommits defaults when not overridden by policy
+  if (conventional.type === "feat") return "minor";
+  if (conventional.type === "fix" || conventional.type === "perf") return "patch";
+  return "none";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/release/scripts/generate-range-notes.mjs` around lines 178 - 205,
detectReleaseType currently hardcodes release mapping for conventional.type
(feat/fix/perf/revert/refactor) instead of consulting the single source of truth
in policyByType (from release-policy.cjs), causing drift; update
detectReleaseType to derive the release kind from
policyByType.get(conventional.type)?.release when conventional is present,
treating the special case that if no explicit policy release exists and
conventional.type === "feat" then return "minor", and still return "major" when
conventional.breaking or findBreakingNotes(body, conventional.subject).length >
0; ensure the final fallback returns "none" only when policy explicitly disables
release or no rule applies (use symbols detectReleaseType, policyByType,
conventional.type, findBreakingNotes to locate changes).

207-216: Defensive: guard against unknown release types in maxReleaseType.

If next is ever a value not in rank (e.g., future addition like "prerelease"), rank.get(next) is undefined and undefined > number is false, so the new value is silently dropped. A cheap guard + thrown error would catch misuse immediately. Optional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/release/scripts/generate-range-notes.mjs` around lines 207 - 216, The
maxReleaseType function should defensively validate release types from the rank
map before comparing; update maxReleaseType to check rank.has(next) and
rank.has(current) (or check rank.get(...) !== undefined) and throw a clear
TypeError including the offending value(s) if an unknown release type is
encountered, so unknown types like "prerelease" are caught instead of silently
dropped.

119-141: Minor: redundant findBreakingNotes invocation per commit.

findBreakingNotes(body, subject) runs during commit construction (line 132), then runs again inside detectReleaseType (line 183) on every commit. For large ranges this doubles the body-line scanning. Consider computing the notes once and passing them into detectReleaseType, or checking breakingNotes.length already attached to the commit. Low impact; nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/release/scripts/generate-range-notes.mjs` around lines 119 - 141, In
parseCommits, avoid calling findBreakingNotes twice by computing const
breakingNotes = findBreakingNotes(body, subject) once when building each commit
object and attaching it to the commit, then update the call to detectReleaseType
to accept or use that precomputed breakingNotes (e.g.,
detectReleaseType(conventional, body, breakingNotes) or have detectReleaseType
check commit.breakingNotes where it's invoked) and update any other callers of
detectReleaseType accordingly so the heavy body scan is not repeated.
.github/workflows/release-candidate.yml (1)

97-113: Minor: redundant always() and unconditional Discord post.

  • if: ${{ always() && needs.generate-notes.result == 'success' }} — the always() is redundant; needs.generate-notes.result == 'success' already implies both upstream jobs completed (since generate-notes depends on resolve_ref).
  • The job fires even when needs.generate-notes.outputs.released == 'true' is false (no releasable commits in range). For manual RC dispatch this is likely intentional (operator asked for the broadcast), but worth confirming — otherwise add && needs.generate-notes.outputs.released == 'true' to suppress empty RC pings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release-candidate.yml around lines 97 - 113, Update the
notify-discord job condition: remove the redundant always() wrapper and set if
to require successful generate-notes (use if: ${{ needs.generate-notes.result ==
'success' }}), and — unless you intentionally want manual RC dispatches to
always post — add the released guard to suppress empty pings by changing the
condition to if: ${{ needs.generate-notes.result == 'success' &&
needs.generate-notes.outputs.released == 'true' }}; reference the notify-discord
job and its if line in the workflow to make this change.
.github/actions/compute-release-notes/action.yml (1)

72-113: LGTM — small note on output size.

Extraction fields (compareUrl, nextVersion, releaseType, released, toSha) match the JSON payload in tools/release/scripts/generate-range-notes.mjs, and the random EOF_<hex> delimiter guards against content collisions in release_notes. Just be aware that release_notes propagated as a composite-action output + reusable-workflow output can approach the 1 MB step output limit for large ranges (e.g., a stale base tag on a very active develop). The artifact upload at lines 131–139 mitigates loss, so this is informational.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/actions/compute-release-notes/action.yml around lines 72 - 113, The
step "Extract Range Outputs" can emit a very large release_notes value and risk
hitting the 1 MB GitHub step output limit; update the logic handling
release_notes (the release_notes<<${release_notes_delim} block that reads
tools/release/range-release-notes.md) to guard against oversized output by
either truncating or compressing the content before appending to GITHUB_OUTPUT
(or alternatively always persist the full file as an artifact and only emit a
small summary to GITHUB_OUTPUT), and ensure the changes reference the same
symbols: the release_notes variable, tools/release/range-release-notes.md, and
the "Generate Range Notes" / "Extract Range Outputs" steps so the output remains
safe for composite-action and reusable-workflow usage.
.github/workflows/notify-discord-release-notes.yml (1)

136-145: Constrain mention parsing for Discord notifications.

CONTENT can include configurable mention_text; add allowed_mentions so a bad variable value does not accidentally ping @everyone/@here while still allowing role/user mentions.

🔒 Suggested payload guard
               {
                 content: $content,
+                allowed_mentions: {
+                  parse: ["users", "roles"]
+                },
                 embeds: [
                   {
                     title: "Build Details",

Please verify this matches the current Discord webhook allowed_mentions behavior if @here/@everyone pings are intentionally supported.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/notify-discord-release-notes.yml around lines 136 - 145,
The Discord webhook payload currently builds an object with content and embeds
(title "Build Details", description $description) but lacks an allowed_mentions
field; update the webhook payload object to include an allowed_mentions property
that explicitly restricts parsing to only "users" and "roles" (or an explicit
list) so user/role pings remain allowed but `@here`/`@everyone` cannot be
triggered by a bad mention_text value; reference the payload object where
content/$content and embeds (title "Build Details", description $description)
are defined and add allowed_mentions accordingly, and verify the final behavior
matches Discord's webhook semantics if `@here`/`@everyone` support is
intentional.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/publish-nightly.yml:
- Around line 57-61: The current check uses git rev-parse which only verifies
existence, not ancestry; change the logic around
previous_nightly_sha/from_ref/base_tag to verify ancestry with git merge-base
--is-ancestor (e.g., git merge-base --is-ancestor "${previous_nightly_sha}"
HEAD) and only set from_ref when that returns success; otherwise fall back to
base_tag but also ensure base_tag is an ancestor (git merge-base --is-ancestor
"${base_tag}" HEAD) before using it, and emit the warning when neither is a
valid ancestor. Use the existing variable names previous_nightly_sha, from_ref,
and base_tag to locate and update the conditional.

In @.github/workflows/release-candidate.yml:
- Around line 52-66: The current_version value is computed from the latest
publish-nightly.yml run on develop regardless of the dispatched candidate_ref,
which can show a misleading nightly identifier when promoting a non-develop ref;
change the logic in this block (variables: current_version, latest_nightly_sha,
latest_nightly_created_at, candidate_ref, base_tag) to only derive the
nightly-style name when candidate_ref == 'develop' and otherwise set
current_version to an RC-style identifier (e.g.,
rc-<YYYYMMDD>-<candidate-short-sha> derived from candidate_sha or use base_tag),
or alternatively rename the output to reflect it always means
"last_published_nightly" if you want to keep the existing behavior; update the
echo to emit the chosen value.

In @.github/workflows/release-main.yml:
- Around line 140-204: The prepare-release-notification job exports a
release_notes output (release_notes) that notify-discord never consumes; either
remove the release_notes export from prepare-release-notification or pass it
into the notify-discord workflow invocation (uses:
./.github/workflows/notify-discord-release-notes.yml) as an input (e.g., add
release_notes: ${{ needs.prepare-release-notification.outputs.release_notes }}
and update the called workflow to accept/use that input), and delete the
echo/printing lines that emit release_notes if you choose to drop the output to
avoid large job-output size issues.

In `@tools/release/scripts/generate-range-notes.mjs`:
- Around line 100-107: coerceVersion currently only matches X.Y.Z and returns
"0.0.0" for tags with prerelease or build metadata (e.g., v1.4.0-rc.1), so
update coerceVersion to strip leading "v"/whitespace and remove any
prerelease/build suffix before matching (or replace the regex approach by using
a semver parser like semver.parse); specifically adjust the function
coerceVersion to first normalize input (trim, remove leading "v") and strip
anything from the first "-" or "+" onward, then extract the three numeric
segments and return them, ensuring baseTag values such as "v1.4.0-rc.1" produce
"1.4.0" instead of "0.0.0".

---

Nitpick comments:
In @.github/actions/compute-release-notes/action.yml:
- Around line 72-113: The step "Extract Range Outputs" can emit a very large
release_notes value and risk hitting the 1 MB GitHub step output limit; update
the logic handling release_notes (the release_notes<<${release_notes_delim}
block that reads tools/release/range-release-notes.md) to guard against
oversized output by either truncating or compressing the content before
appending to GITHUB_OUTPUT (or alternatively always persist the full file as an
artifact and only emit a small summary to GITHUB_OUTPUT), and ensure the changes
reference the same symbols: the release_notes variable,
tools/release/range-release-notes.md, and the "Generate Range Notes" / "Extract
Range Outputs" steps so the output remains safe for composite-action and
reusable-workflow usage.

In @.github/workflows/notify-discord-release-notes.yml:
- Around line 136-145: The Discord webhook payload currently builds an object
with content and embeds (title "Build Details", description $description) but
lacks an allowed_mentions field; update the webhook payload object to include an
allowed_mentions property that explicitly restricts parsing to only "users" and
"roles" (or an explicit list) so user/role pings remain allowed but
`@here`/`@everyone` cannot be triggered by a bad mention_text value; reference
the payload object where content/$content and embeds (title "Build Details",
description $description) are defined and add allowed_mentions accordingly, and
verify the final behavior matches Discord's webhook semantics if
`@here`/`@everyone` support is intentional.

In @.github/workflows/release-candidate.yml:
- Around line 97-113: Update the notify-discord job condition: remove the
redundant always() wrapper and set if to require successful generate-notes (use
if: ${{ needs.generate-notes.result == 'success' }}), and — unless you
intentionally want manual RC dispatches to always post — add the released guard
to suppress empty pings by changing the condition to if: ${{
needs.generate-notes.result == 'success' &&
needs.generate-notes.outputs.released == 'true' }}; reference the notify-discord
job and its if line in the workflow to make this change.

In `@tools/release/scripts/generate-range-notes.mjs`:
- Around line 178-205: detectReleaseType currently hardcodes release mapping for
conventional.type (feat/fix/perf/revert/refactor) instead of consulting the
single source of truth in policyByType (from release-policy.cjs), causing drift;
update detectReleaseType to derive the release kind from
policyByType.get(conventional.type)?.release when conventional is present,
treating the special case that if no explicit policy release exists and
conventional.type === "feat" then return "minor", and still return "major" when
conventional.breaking or findBreakingNotes(body, conventional.subject).length >
0; ensure the final fallback returns "none" only when policy explicitly disables
release or no rule applies (use symbols detectReleaseType, policyByType,
conventional.type, findBreakingNotes to locate changes).
- Around line 207-216: The maxReleaseType function should defensively validate
release types from the rank map before comparing; update maxReleaseType to check
rank.has(next) and rank.has(current) (or check rank.get(...) !== undefined) and
throw a clear TypeError including the offending value(s) if an unknown release
type is encountered, so unknown types like "prerelease" are caught instead of
silently dropped.
- Around line 119-141: In parseCommits, avoid calling findBreakingNotes twice by
computing const breakingNotes = findBreakingNotes(body, subject) once when
building each commit object and attaching it to the commit, then update the call
to detectReleaseType to accept or use that precomputed breakingNotes (e.g.,
detectReleaseType(conventional, body, breakingNotes) or have detectReleaseType
check commit.breakingNotes where it's invoked) and update any other callers of
detectReleaseType accordingly so the heavy body scan is not repeated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 773dd9cf-ea8e-442e-a183-257648ffe15e

📥 Commits

Reviewing files that changed from the base of the PR and between e6eaaea and 8e97ed2.

⛔ Files ignored due to path filters (1)
  • tools/release/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (10)
  • .github/actions/compute-release-notes/action.yml
  • .github/workflows/notify-discord-release-notes.yml
  • .github/workflows/publish-nightly.yml
  • .github/workflows/release-candidate.yml
  • .github/workflows/release-main.yml
  • tools/release/Justfile
  • tools/release/package.json
  • tools/release/release-policy.cjs
  • tools/release/release.config.cjs
  • tools/release/scripts/generate-range-notes.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Analyze (java-kotlin)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (11)
.github/workflows/release-candidate.yml (1)

82-95: Nit: redundant pre-checkout before composite action.

The Checkout Workflow Repository step at fetch-depth 1 is immediately superseded by the composite action's own actions/checkout@v6 at fetch-depth 0 with ref: to_ref. You can drop this step — the composite already brings in the repo (including its own action.yml) once GitHub resolves uses: ./.github/actions/compute-release-notes… actually that resolution requires the workflow repo checkout, so this step is needed to locate the local action. Keep as-is; ignore this nit.

tools/release/Justfile (1)

18-20: LGTM.

The new range-notes recipe cleanly wires to the release:range-notes yarn script and aligns with the composite action consumers.

tools/release/package.json (1)

11-18: LGTM.

Script addition and plugin devDependencies align with the new range-notes tooling and shared release policy usage in release.config.cjs.

tools/release/release.config.cjs (1)

18-20: Behavior change: refactor and revert now trigger patch releases.

The derived releaseRules emits { release: "patch", type: "refactor" } and { release: "patch", type: "revert" } (and explicit release: false for chore/docs/ci/build/test/style). Under the default conventionalcommits preset, refactor/revert would not normally produce a release, so merging one of these types into main will now cut a patch release on the next semantic-release run. Please confirm this is intentional and aligned with the RC/nightly flows that derive release type from the same policy.

tools/release/release-policy.cjs (1)

1-21: LGTM.

Clean extraction of shared release policy. Consumed consistently by release.config.cjs and generate-range-notes.mjs.

.github/workflows/notify-discord-release-notes.yml (2)

60-120: Message composition looks solid.

The channel-specific header, fallback URL selection, and multiline $GITHUB_OUTPUT handling are clear and match the caller context.


146-191: Webhook send path looks good.

The conditional changelog button and failing curl on non-2xx responses should make notification failures visible.

.github/workflows/publish-nightly.yml (4)

7-20: Manual smoke-test controls look good.

The publish_image input plus minimal actions: read / contents: read permissions fit the new nightly notification flow.


68-138: Nightly image metadata flow looks consistent.

The conditional publish path and recorded nightly_tag output line up with the Docker tag/build-arg usage.


169-196: Notification gating matches the intended nightly flow.

The job correctly allows Discord notifications after notes generation when image publishing either succeeds or is intentionally skipped.


155-168: The shallow checkout in generate-notes is not a problem. The compute-release-notes composite action performs its own checkout with fetch-depth: 0 and explicitly checks out the target ref (to_ref), making it self-contained and independent of the parent job's checkout depth. The action already has access to the full git history and tags needed to generate the changelog range properly.

			> Likely an incorrect or invalid review comment.

Comment thread .github/workflows/publish-nightly.yml Outdated
Comment on lines +57 to +61
if [ -n "$previous_nightly_sha" ] && git rev-parse --verify --quiet "${previous_nightly_sha}^{commit}" >/dev/null; then
from_ref="$previous_nightly_sha"
elif [ -n "$previous_nightly_sha" ]; then
echo "::warning::Ignoring invalid nightly baseline ${previous_nightly_sha}; using ${base_tag}"
fi

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.

⚠️ Potential issue | 🟡 Minor

Verify the previous nightly SHA is actually an ancestor.

rev-parse only proves the commit exists locally. If develop was rewritten, using a non-ancestor nightly SHA can generate misleading changelog ranges; fall back to base_tag unless it is on the current history.

🛠 Suggested ancestry check
-          if [ -n "$previous_nightly_sha" ] && git rev-parse --verify --quiet "${previous_nightly_sha}^{commit}" >/dev/null; then
-            from_ref="$previous_nightly_sha"
-          elif [ -n "$previous_nightly_sha" ]; then
-            echo "::warning::Ignoring invalid nightly baseline ${previous_nightly_sha}; using ${base_tag}"
-          fi
+          if [ -n "$previous_nightly_sha" ] && git rev-parse --verify --quiet "${previous_nightly_sha}^{commit}" >/dev/null; then
+            if git merge-base --is-ancestor "$previous_nightly_sha" HEAD; then
+              from_ref="$previous_nightly_sha"
+            else
+              echo "::warning::Ignoring non-ancestor nightly baseline ${previous_nightly_sha}; using ${base_tag}"
+            fi
+          elif [ -n "$previous_nightly_sha" ]; then
+            echo "::warning::Ignoring invalid nightly baseline ${previous_nightly_sha}; using ${base_tag}"
+          fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -n "$previous_nightly_sha" ] && git rev-parse --verify --quiet "${previous_nightly_sha}^{commit}" >/dev/null; then
from_ref="$previous_nightly_sha"
elif [ -n "$previous_nightly_sha" ]; then
echo "::warning::Ignoring invalid nightly baseline ${previous_nightly_sha}; using ${base_tag}"
fi
if [ -n "$previous_nightly_sha" ] && git rev-parse --verify --quiet "${previous_nightly_sha}^{commit}" >/dev/null; then
if git merge-base --is-ancestor "$previous_nightly_sha" HEAD; then
from_ref="$previous_nightly_sha"
else
echo "::warning::Ignoring non-ancestor nightly baseline ${previous_nightly_sha}; using ${base_tag}"
fi
elif [ -n "$previous_nightly_sha" ]; then
echo "::warning::Ignoring invalid nightly baseline ${previous_nightly_sha}; using ${base_tag}"
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-nightly.yml around lines 57 - 61, The current
check uses git rev-parse which only verifies existence, not ancestry; change the
logic around previous_nightly_sha/from_ref/base_tag to verify ancestry with git
merge-base --is-ancestor (e.g., git merge-base --is-ancestor
"${previous_nightly_sha}" HEAD) and only set from_ref when that returns success;
otherwise fall back to base_tag but also ensure base_tag is an ancestor (git
merge-base --is-ancestor "${base_tag}" HEAD) before using it, and emit the
warning when neither is a valid ancestor. Use the existing variable names
previous_nightly_sha, from_ref, and base_tag to locate and update the
conditional.

Comment on lines +52 to +66
latest_nightly_json="$(
gh api "/repos/${GITHUB_REPOSITORY}/actions/workflows/publish-nightly.yml/runs?branch=develop&status=success&per_page=1"
)"
latest_nightly_sha="$(printf '%s' "$latest_nightly_json" | jq -r '.workflow_runs[0].head_sha // ""')"
latest_nightly_created_at="$(printf '%s' "$latest_nightly_json" | jq -r '.workflow_runs[0].created_at // ""')"
current_version="$base_tag"
if [ -n "$latest_nightly_sha" ] && [ -n "$latest_nightly_created_at" ]; then
latest_nightly_date="$(date -u -d "$latest_nightly_created_at" +%Y%m%d)"
latest_nightly_short_sha="$(printf '%s' "$latest_nightly_sha" | cut -c1-7)"
current_version="nightly-${latest_nightly_date}-${latest_nightly_short_sha}"
fi

echo "candidate_sha=${candidate_sha}" >> "$GITHUB_OUTPUT"
echo "base_tag=${base_tag}" >> "$GITHUB_OUTPUT"
echo "current_version=${current_version}" >> "$GITHUB_OUTPUT"

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.

⚠️ Potential issue | 🟡 Minor

current_version semantics may not match the dispatched candidate_ref.

current_version is computed from the latest successful publish-nightly.yml run on develop (regardless of the selected candidate_ref). When an operator dispatches an RC against a non-develop ref (e.g., a feature branch or a specific SHA), the Discord embed will show nightly-YYYYMMDD-<develop-sha> as the "current version", which has no relation to the candidate being promoted. Consider:

  • gating the nightly-derived identifier on candidate_ref == 'develop', and otherwise using a rc-<date>-<candidate-short-sha> identifier; or
  • documenting clearly that this field always reflects "last published nightly" and renaming it accordingly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release-candidate.yml around lines 52 - 66, The
current_version value is computed from the latest publish-nightly.yml run on
develop regardless of the dispatched candidate_ref, which can show a misleading
nightly identifier when promoting a non-develop ref; change the logic in this
block (variables: current_version, latest_nightly_sha,
latest_nightly_created_at, candidate_ref, base_tag) to only derive the
nightly-style name when candidate_ref == 'develop' and otherwise set
current_version to an RC-style identifier (e.g.,
rc-<YYYYMMDD>-<candidate-short-sha> derived from candidate_sha or use base_tag),
or alternatively rename the output to reflect it always means
"last_published_nightly" if you want to keep the existing behavior; update the
echo to emit the chosen value.

Comment thread .github/workflows/release-main.yml
Comment thread tools/release/scripts/generate-range-notes.mjs

@balazs-szucs balazs-szucs 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.

thank you :)

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

♻️ Duplicate comments (1)
.github/workflows/publish-nightly.yml (1)

46-63: ⚠️ Potential issue | 🟡 Minor

Validate base_tag before using it as the fallback range start.

The previous nightly SHA is now ancestry-checked, but when it is absent or unusable, from_ref remains base_tag. If that stable tag is not on develop history, the nightly range can still be misleading.

🛠️ Suggested fallback hardening
           base_tag="$(git describe --tags --abbrev=0 --match 'v*' origin/main)"
           from_ref="$base_tag"
+          if ! git merge-base --is-ancestor "$base_tag" HEAD; then
+            base_merge="$(git merge-base "$base_tag" HEAD || true)"
+            if [ -z "$base_merge" ]; then
+              echo "::error::Stable base ${base_tag} is unrelated to HEAD"
+              exit 1
+            fi
+            echo "::warning::Stable base ${base_tag} is not an ancestor of HEAD; using merge-base ${base_merge}"
+            from_ref="$base_merge"
+          fi
           previous_nightly_sha=""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-nightly.yml around lines 46 - 63, Validate that
base_tag is a reachable ancestor of the current branch before using it as the
fallback for from_ref: check that base_tag exists (git rev-parse --verify) and
is an ancestor of HEAD (git merge-base --is-ancestor); if either check fails,
emit a warning and choose a safer fallback (for example use the repository root
commit or leave from_ref unset/use HEAD's first parent commit) instead of
blindly using base_tag. Update the logic around base_tag, from_ref, and
previous_nightly_sha to perform these checks and set from_ref only when base_tag
is verified as a valid ancestor; keep the existing warnings for unusable
baselines and ensure process uses the verified symbol names base_tag, from_ref,
and previous_nightly_sha.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/publish-nightly.yml:
- Around line 157-169: The checkout step named "Checkout Workflow Repository"
currently does a shallow fetch (fetch-depth: 1) which conflicts with the
downstream compute-release-notes action; remove the shallow fetch and add an
explicit ref so the job checks out the exact commit used by the notes action —
update the "Checkout Workflow Repository" actions/checkout step to use
fetch-depth: 0 (or remove fetch-depth) and include ref: ${{
needs.resolve_ref.outputs.commit_sha }} so the subsequent "Build Full Changelog"
(id: notes, uses: ./.github/actions/compute-release-notes) runs against the
correct commit.

In `@tools/release/scripts/generate-range-notes.mjs`:
- Around line 124-134: The breakingNotes currently only come from footer parsing
via findBreakingNotes(body, subject), which misses commits marked with a bang in
the subject (conventional.breaking). Update the map logic that builds each entry
(where parseConventionalSubject is used, detectReleaseType is called, and
breakingNotes is set) to fallback to using the subject as a breaking note when
conventional?.breaking is true and findBreakingNotes returned empty; i.e., set
breakingNotes to the result of findBreakingNotes(...) || (conventional?.breaking
? [subject] : []), so feat!: / fix!: commits produce a Breaking Changes entry
even without footers.

---

Duplicate comments:
In @.github/workflows/publish-nightly.yml:
- Around line 46-63: Validate that base_tag is a reachable ancestor of the
current branch before using it as the fallback for from_ref: check that base_tag
exists (git rev-parse --verify) and is an ancestor of HEAD (git merge-base
--is-ancestor); if either check fails, emit a warning and choose a safer
fallback (for example use the repository root commit or leave from_ref unset/use
HEAD's first parent commit) instead of blindly using base_tag. Update the logic
around base_tag, from_ref, and previous_nightly_sha to perform these checks and
set from_ref only when base_tag is verified as a valid ancestor; keep the
existing warnings for unusable baselines and ensure process uses the verified
symbol names base_tag, from_ref, and previous_nightly_sha.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 171b9ab7-c9a5-4795-afad-e4a38640a9e5

📥 Commits

Reviewing files that changed from the base of the PR and between 8e97ed2 and c9122dc.

📒 Files selected for processing (3)
  • .github/workflows/publish-nightly.yml
  • .github/workflows/release-main.yml
  • tools/release/scripts/generate-range-notes.mjs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Test Suite / Backend Tests
  • GitHub Check: Test Suite / Frontend Tests
  • GitHub Check: Analyze (java-kotlin)
🔇 Additional comments (1)
.github/workflows/release-main.yml (1)

134-197: Stable Discord notification flow looks sound.

The notification is gated after release publishing, derives stable release metadata, and forwards the consumed outputs to the reusable Discord workflow.

Comment thread .github/workflows/publish-nightly.yml
Comment on lines +124 to +134
.map((entry) => {
const [hash, subject, body = ""] = entry.split("\x1f");
const conventional = parseConventionalSubject(subject);
const type = conventional?.type || "";
const policy = policyByType.get(type);

return {
body,
breakingNotes: findBreakingNotes(body, subject),
hash,
releaseType: detectReleaseType(conventional, body),

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.

⚠️ Potential issue | 🟡 Minor

Populate breaking notes for ! commits without footers.

detectReleaseType marks feat!:/fix!: commits as major, but breakingNotes only comes from body footers, so the rendered changelog can omit the “Breaking Changes” section for those commits. Use the subject as a fallback note when conventional.breaking is true.

🐛 Proposed fix
       const [hash, subject, body = ""] = entry.split("\x1f");
       const conventional = parseConventionalSubject(subject);
+      const footerBreakingNotes = findBreakingNotes(body, subject);
+      const breakingNotes =
+        conventional?.breaking && footerBreakingNotes.length === 0
+          ? [conventional.subject || subject]
+          : footerBreakingNotes;
       const type = conventional?.type || "";
       const policy = policyByType.get(type);
 
       return {
         body,
-        breakingNotes: findBreakingNotes(body, subject),
+        breakingNotes,
         hash,
         releaseType: detectReleaseType(conventional, body),

Also applies to: 178-185, 236-245

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/release/scripts/generate-range-notes.mjs` around lines 124 - 134, The
breakingNotes currently only come from footer parsing via
findBreakingNotes(body, subject), which misses commits marked with a bang in the
subject (conventional.breaking). Update the map logic that builds each entry
(where parseConventionalSubject is used, detectReleaseType is called, and
breakingNotes is set) to fallback to using the subject as a breaking note when
conventional?.breaking is true and findBreakingNotes returned empty; i.e., set
breakingNotes to the result of findBreakingNotes(...) || (conventional?.breaking
? [subject] : []), so feat!: / fix!: commits produce a Breaking Changes entry
even without footers.

@zachyale zachyale force-pushed the feat/add-automated-gh-release-notifications branch from c9122dc to 9f20cf5 Compare April 17, 2026 23:17
@coderabbitai coderabbitai Bot requested a review from imajes April 17, 2026 23:18
@zachyale zachyale force-pushed the feat/add-automated-gh-release-notifications branch from 9f20cf5 to 50bf8c4 Compare April 17, 2026 23:21
@zachyale zachyale merged commit c5f1ad7 into develop Apr 17, 2026
7 checks passed
@zachyale zachyale deleted the feat/add-automated-gh-release-notifications branch April 17, 2026 23:21
@obviouslyallie obviouslyallie mentioned this pull request Apr 18, 2026
1 task
dsmouse pushed a commit to dsmouse/grimmory that referenced this pull request May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants