feat: Generate Linear release notes from WebGUI tags#2648
Conversation
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.
WalkthroughThis 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. ChangesLinear Release Sync Enhancement
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
.github/scripts/sync-linear-release.mjs (1)
540-565: ⚖️ Poor tradeoffPagination limit may miss issues for large releases.
findIssuesForReleasefetches onlyfirst: 100issues. 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
📒 Files selected for processing (2)
.github/scripts/sync-linear-release.mjs.github/workflows/linear-release.yml
| 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 |
There was a problem hiding this comment.
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.
| 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.
Summary
Validation
node --check .github/scripts/sync-linear-release.mjsruby -e 'require "yaml"; YAML.load_file(".github/workflows/linear-release.yml"); puts "yaml ok"'Summary by CodeRabbit
Release Notes
New Features
Chores