Skip to content

feat: Generate Linear release notes from WebGUI tags#2648

Open
elibosley wants to merge 6 commits into
masterfrom
codex/webgui-linear-release-notes
Open

feat: Generate Linear release notes from WebGUI tags#2648
elibosley wants to merge 6 commits into
masterfrom
codex/webgui-linear-release-notes

Conversation

@elibosley
Copy link
Copy Markdown
Member

@elibosley elibosley commented May 22, 2026

Summary

  • Generate marker-managed Linear release notes from the active WebGUI tag commit range.
  • Capture PR summaries in the workflow and pass them to the Linear sync script.
  • Include release metadata, PR links, linked Linear issue IDs, FeatureOS links, and commit fallback content.

Validation

  • node --check .github/scripts/sync-linear-release.mjs
  • ruby -e 'require "yaml"; YAML.load_file(".github/workflows/linear-release.yml"); puts "yaml ok"'

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for coordinating stable and prerelease releases across multiple release tracks, improving version relationship tracking.
    • Enhanced release notes management with automated formatting and synchronization of linked issues and pull requests.
  • Chores

    • Improved release workflow with enhanced GitHub PR metadata collection and integration.

Review Change Stack

elibosley added 6 commits May 22, 2026 12:13
Purpose of the change:
- Address review feedback on the WebGUI Linear release workflow while keeping the patch minimal.

Previous behavior:
- The workflow used the mutable actions/checkout@v6 tag.
- Tags without a previous semver tag used RANGE_SPEC equal to the tag name, causing git log to walk full ancestry.
- PR metadata collection only recognized merge commits shaped as Merge pull request #123.

Why that was a problem:
- Mutable action tags can be retargeted upstream.
- A first-tag release could scan unrelated historical commits and attach unrelated Linear metadata.
- Squash-merged PRs with commit messages like Fix thing (#123) would not have their PR body scanned for Linear or FeatureOS links.

What this changes:
- Pins actions/checkout to the current v6.0.2 commit SHA.
- Uses TAG^..TAG for first-tag ranges when a parent exists, with the old tag fallback only for parentless commits.
- Extends PR number extraction to include squash-merge (#123) patterns while preserving numeric extraction, sorting, and dedupe.

How it works:
- The existing PR lookup loop remains unchanged; it receives a broader but still numeric-only PR number list.
- The git range emitted by Resolve tag remains bounded before the later git log call uses it.
Purpose of the change:
- Support WebGUI release sync when the only issue relationship is stored in Linear as a GitHub PR attachment.

Previous behavior:
- The workflow scanned commit and PR text for Linear issue IDs and FeatureOS URLs only.
- If a PR was linked from Linear but did not mention the Linear ticket in GitHub, release sync could miss the issue.

Why that was a problem:
- WebGUI release membership would be incomplete for issues where the canonical link lives in Linear's GitHub integration attachments.

What this changes:
- Records the GitHub PR URL for each PR discovered from the release tag range.
- Passes those PR URLs into the Linear sync script.
- Resolves matching Linear attachments with attachmentsForURL and attaches the linked issues to the release.
- Treats missing PR attachments as normal and avoids noisy skipped entries.

How it works:
- The existing PR number parsing and PR metadata fetch stay in place.
- The script uses the same attachment lookup path for FeatureOS URLs and GitHub PR URLs, then dedupes issues before applying addedReleaseIds.
Purpose of the change:
- Address review feedback on the WebGUI Linear release workflow PR parsing.

Previous behavior:
- PR number extraction matched both explicit merge commits and any parenthetical number shaped like (#123).

Why that was a problem:
- Ordinary issue references in commit messages could be treated as PR numbers and sent to the GitHub pulls API, causing the workflow to fail.

What this changes:
- Restricts PR number extraction to explicit Merge pull request #123 commit messages.
- Keeps the existing numeric extraction, sort -u dedupe, and || true fallback behavior.

How it works:
- The first grep now emits only Merge pull request #[0-9]+ matches before the second grep extracts digits for the existing curl loop.
Purpose of the change:
- Make workflow_dispatch testing work for historical WebGUI release tags.

Previous behavior:
- Manual runs checked out the requested tag directly.
- Historical tags do not contain the new sync script, so the workflow failed with MODULE_NOT_FOUND.

Why that was a problem:
- The workflow could not be tested against previous tags from the PR branch even though the tag metadata was fetched successfully.

What this changes:
- Manual workflow_dispatch runs now check out the workflow ref/branch while still resolving and operating on the requested tag_name.
- Tag push runs continue to check out the pushed tag.

How it works:
- The checkout ref uses github.ref for workflow_dispatch and github.ref_name for tag pushes.
Purpose of the change:
- Keep active QA work attached to the next prerelease and stable companion release from WebGUI tag sync.

Previous behavior:
- WebGUI release sync attached discovered Linear issues only to the exact tag release.
- Existing QA Ready issues already on a prerelease were not swept into the next planned prerelease.
- WebGUI-linked prerelease issues could miss the stable companion release.

Why that was a problem:
- QA Ready work could fall out of the next release bucket even though it still needed validation.
- Stable release planning would not reliably track all work that passed through the prerelease series.

What this changes:
- Internal/prerelease tag sync now resolves the exact prerelease, stable companion, and next planned prerelease releases.
- Active issues are attached to all applicable release buckets.
- Completed/internal-released issues are removed from the next planned prerelease while preserving exact prerelease and stable companion memberships.
- Existing issues already attached to the exact prerelease are swept through the same policy, even if they were not discovered from the current tag diff.

How it works:
- The sync script creates or updates the stable companion as Planned and the next prerelease as Planned.
- Issue state is loaded from Linear and used to decide whether to carry or remove next-prerelease membership.
- Linear issue updates use addedReleaseIds and removedReleaseIds without changing workflow state.
- Purpose: let the webgui tag workflow own user-facing Linear release notes for the active tag.\n- Before: the workflow synced Linear releases and issue membership but did not write release notes from the tag's commit or PR range.\n- Problem: stable release notes were being filled by release-diff internals instead of webgui-facing release context.\n- New behavior: the workflow records PR summaries, linked Linear IDs, FeatureOS links, and commit metadata, then syncs an idempotent Version <tag> release note.\n- How it works: the collection step emits PR summary and log paths, and the Node sync script creates or updates a marker-managed Linear release note attached to the active tag release.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

Walkthrough

This PR enhances Linear release automation to coordinate multiple release tracks and enrich release notes with GitHub PR context. The workflow now collects PR URLs and metadata during commit analysis, passing them to the sync script. The script resolves companion stable and next-prerelease releases, syncs managed release notes incorporating PR summaries and linked issues, and reconciles issue membership across all related releases with state-based carry-forward logic.

Changes

Linear Release Sync Enhancement

Layer / File(s) Summary
Workflow PR metadata collection and wiring
.github/workflows/linear-release.yml
Workflow pins actions/checkout, improves tag range detection (${TAG_NAME}^..${TAG_NAME}), collects GitHub PR URLs and markdown PR summaries, outputs these artifacts with counts, and passes them to the sync script via environment variables.
Script initialization and related release resolution
.github/scripts/sync-linear-release.mjs
New constants define stable release pipeline and channel-specific stage names. Script loads PR URLs and summaries from environment paths, computes stable and optional next-prerelease companion releases via resolveRelatedReleases, and generalizes upsertRelease to update explicit name/version/description/commitSha.
GraphQL schema expansion for release notes
.github/scripts/sync-linear-release.mjs
findRelease, createRelease, and updateRelease GraphQL operations now include releaseNotes object shape (id, title, documentContent, content) in returned release data.
Release notes content synchronization
.github/scripts/sync-linear-release.mjs
Script builds formatted release notes from tag/commit metadata, PR summaries, linked issue IDs, FeatureOS URLs, and (fallback) commit subjects. Creates or updates managed release notes using deterministic HTML comment markers for idempotent repeated runs. Includes utilities for optional file reading, commit-subject extraction with deduping, and marker-safe section rendering.
Multi-release issue synchronization and carry-forward logic
.github/scripts/sync-linear-release.mjs
syncIssuesToRelease now accepts relatedReleases and githubPrUrls for richer issue sources (GitHub PR URLs and all currently linked issues). New syncIssueToReleases replaces single-release logic by reconciling issue membership across primary, stable, and next-prerelease releases; carries issues to next prerelease only if their state is non-terminal. Includes findIssuesForRelease for fetching linked issues, shouldCarryIssueToNextPrerelease for state-based eligibility, and version-derivation helpers.

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions as GitHub Actions
  participant SyncScript as sync-linear-release.mjs
  participant LinearAPI as Linear GraphQL
  GitHubActions->>SyncScript: PR URLs, PR summaries, log paths
  SyncScript->>LinearAPI: resolveRelatedReleases → upsert stable + next-prerelease
  LinearAPI-->>SyncScript: stable & next releases
  SyncScript->>LinearAPI: build release notes from PR summaries + issues
  SyncScript->>LinearAPI: upsert managed release note (HTML markers)
  LinearAPI-->>SyncScript: release note created/updated
  SyncScript->>LinearAPI: for each issue: syncIssueToReleases across primary/stable/next
  LinearAPI-->>SyncScript: issue memberships updated
  SyncScript->>GitHubActions: summary with PR count
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • unraid/webgui#2646: Both update the same Linear sync machinery with multi-release coordination logic for different release channels, extending prior tag-to-OS syncing.
  • unraid/webgui#2644: Both modify .github/workflows/linear-release.yml; this PR extends it with PR URL/summary/log artifact collection and passing.
  • unraid/webgui#2647: Builds on the same Linear release workflow/script changes including checkout pinning, tag-range resolution, and issue syncing refactor via syncIssueToReleases.

Poem

🐇 A script that syncs and knows the way,
Through stable tracks and prerelease play,
With PR notes woven, states reviewed,
Each issue's path through releases grooved,
Linear releases now coordinated with joy!

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Generate Linear release notes from WebGUI tags' clearly and specifically summarizes the main change: adding generation of Linear release notes from WebGUI tag commit ranges, which is the core objective described in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 codex/webgui-linear-release-notes
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch codex/webgui-linear-release-notes

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/scripts/sync-linear-release.mjs (1)

540-565: ⚖️ Poor tradeoff

Pagination limit may miss issues for large releases.

findIssuesForRelease fetches only first: 100 issues. If a release has more than 100 linked issues, subsequent issues won't be processed for carry-forward logic.

Consider either documenting this as an acceptable limitation (releases rarely exceed 100 issues) or implementing pagination. For now, this is likely sufficient for the expected scale.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/sync-linear-release.mjs around lines 540 - 565,
findIssuesForRelease currently requests only the first 100 issues (query uses
issues(first: 100)), which can drop issues for large releases; update the
function (findIssuesForRelease) to paginate through issues using the GraphQL
cursor-based pagination (use issues(first, after) and loop/recursively call
graphql until pageInfo.hasNextPage is false, aggregating nodes) so all linked
issues for the given releaseId are returned, or alternatively add a clear
comment documenting the 100-item limit if you intend to keep the current
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/linear-release.yml:
- Around line 132-151: The loop over PR_NUMBERS lacks handling for failed curl
requests which leaves PR_JSON_PATH empty/invalid and breaks jq/PR_TITLE logic;
after the curl call that writes to PR_JSON_PATH, check its exit status and/or
validate the file (e.g., ensure valid JSON or non-empty) and if the fetch failed
skip this PR_NUMBER with a logged warning, do not run jq or printf for that PR,
and still cleanup PR_JSON_PATH; update the block referencing curl, PR_JSON_PATH,
jq, PR_TITLE and PR_SUMMARY_PATH to perform this validation and early-continue
on failure.

---

Nitpick comments:
In @.github/scripts/sync-linear-release.mjs:
- Around line 540-565: findIssuesForRelease currently requests only the first
100 issues (query uses issues(first: 100)), which can drop issues for large
releases; update the function (findIssuesForRelease) to paginate through issues
using the GraphQL cursor-based pagination (use issues(first, after) and
loop/recursively call graphql until pageInfo.hasNextPage is false, aggregating
nodes) so all linked issues for the given releaseId are returned, or
alternatively add a clear comment documenting the 100-item limit if you intend
to keep the current behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9cbee431-5bff-4bdd-913a-b2fd4db6dda6

📥 Commits

Reviewing files that changed from the base of the PR and between 7b789bb and de06bf8.

📒 Files selected for processing (2)
  • .github/scripts/sync-linear-release.mjs
  • .github/workflows/linear-release.yml

Comment on lines 132 to 151
for PR_NUMBER in $PR_NUMBERS; do
PR_URL="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
PR_JSON_PATH="$(mktemp)"
echo "${PR_URL}" >> "$GITHUB_PR_URLS_PATH"
curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL:-https://api.github.com}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
| jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
>> "$PR_TEXT_PATH"
> "$PR_JSON_PATH"
jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
"$PR_JSON_PATH" >> "$PR_TEXT_PATH"
PR_TITLE="$(jq -r '.title // ""' "$PR_JSON_PATH")"
if [ -n "$PR_TITLE" ]; then
printf -- '- [#%s %s](%s)\n' "$PR_NUMBER" "$PR_TITLE" "$PR_URL" >> "$PR_SUMMARY_PATH"
else
printf -- '- [#%s](%s)\n' "$PR_NUMBER" "$PR_URL" >> "$PR_SUMMARY_PATH"
fi
rm -f "$PR_JSON_PATH"
done
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 | ⚡ Quick win

Missing error handling for failed PR API requests.

If curl fails (e.g., PR deleted, rate limiting, network error), the -f flag causes silent failure but the loop continues. The subsequent jq commands will fail or produce unexpected output on an empty/invalid JSON file, and rm -f will silently remove the temp file without logging the failure.

Consider adding error handling to skip problematic PRs gracefully:

Proposed fix
           for PR_NUMBER in $PR_NUMBERS; do
             PR_URL="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
             PR_JSON_PATH="$(mktemp)"
             echo "${PR_URL}" >> "$GITHUB_PR_URLS_PATH"
-            curl -fsSL \
+            if ! curl -fsSL \
               -H "Accept: application/vnd.github+json" \
               -H "Authorization: Bearer ${GH_TOKEN}" \
               -H "X-GitHub-Api-Version: 2022-11-28" \
               "${GITHUB_API_URL:-https://api.github.com}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
-              > "$PR_JSON_PATH"
+              > "$PR_JSON_PATH"; then
+              echo "Warning: Failed to fetch PR #${PR_NUMBER}, skipping" >&2
+              rm -f "$PR_JSON_PATH"
+              continue
+            fi
             jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
📝 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
for PR_NUMBER in $PR_NUMBERS; do
PR_URL="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
PR_JSON_PATH="$(mktemp)"
echo "${PR_URL}" >> "$GITHUB_PR_URLS_PATH"
curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL:-https://api.github.com}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
| jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
>> "$PR_TEXT_PATH"
> "$PR_JSON_PATH"
jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
"$PR_JSON_PATH" >> "$PR_TEXT_PATH"
PR_TITLE="$(jq -r '.title // ""' "$PR_JSON_PATH")"
if [ -n "$PR_TITLE" ]; then
printf -- '- [#%s %s](%s)\n' "$PR_NUMBER" "$PR_TITLE" "$PR_URL" >> "$PR_SUMMARY_PATH"
else
printf -- '- [#%s](%s)\n' "$PR_NUMBER" "$PR_URL" >> "$PR_SUMMARY_PATH"
fi
rm -f "$PR_JSON_PATH"
done
for PR_NUMBER in $PR_NUMBERS; do
PR_URL="${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}"
PR_JSON_PATH="$(mktemp)"
echo "${PR_URL}" >> "$GITHUB_PR_URLS_PATH"
if ! curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL:-https://api.github.com}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" \
> "$PR_JSON_PATH"; then
echo "Warning: Failed to fetch PR #${PR_NUMBER}, skipping" >&2
rm -f "$PR_JSON_PATH"
continue
fi
jq -r '[.title, .body, .head.ref, .base.ref] | map(select(. != null and . != "")) | .[]' \
"$PR_JSON_PATH" >> "$PR_TEXT_PATH"
PR_TITLE="$(jq -r '.title // ""' "$PR_JSON_PATH")"
if [ -n "$PR_TITLE" ]; then
printf -- '- [#%s %s](%s)\n' "$PR_NUMBER" "$PR_TITLE" "$PR_URL" >> "$PR_SUMMARY_PATH"
else
printf -- '- [#%s](%s)\n' "$PR_NUMBER" "$PR_URL" >> "$PR_SUMMARY_PATH"
fi
rm -f "$PR_JSON_PATH"
done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/linear-release.yml around lines 132 - 151, The loop over
PR_NUMBERS lacks handling for failed curl requests which leaves PR_JSON_PATH
empty/invalid and breaks jq/PR_TITLE logic; after the curl call that writes to
PR_JSON_PATH, check its exit status and/or validate the file (e.g., ensure valid
JSON or non-empty) and if the fetch failed skip this PR_NUMBER with a logged
warning, do not run jq or printf for that PR, and still cleanup PR_JSON_PATH;
update the block referencing curl, PR_JSON_PATH, jq, PR_TITLE and
PR_SUMMARY_PATH to perform this validation and early-continue on failure.

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.

1 participant