You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build, test, and release gh-aw extension, then generate and prepend release highlights
true
roles
workflow_dispatch
admin
maintainer
inputs
release_type
description
required
type
default
options
Release type (patch, minor, or major)
true
choice
patch
patch
minor
major
permissions
contents
pull-requests
actions
issues
read
read
read
read
engine
copilot
timeout-minutes
20
network
allowed
defaults
node
github.github.com
safe-outputs
update-release
imports
shared/community-attribution.md
jobs
config
push_tag
sync_actions
release
needs
runs-on
outputs
steps
pre_activation
activation
ubuntu-latest
release_tag
${{ steps.compute_config.outputs.release_tag }}
name
uses
with
Checkout repository
actions/checkout@v6.0.2
fetch-depth
persist-credentials
0
false
name
id
uses
with
Compute release configuration
compute_config
actions/github-script@v8
script
const releaseType = context.payload.inputs.release_type;
console.log(`Computing next version for release type: ${releaseType}`);
// Get all releases and sort by semver to find the actual latest version
const { data: releases } = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100
});
// Parse semver and sort releases by version (newest first)
const parseSemver = (tag) => {
const match = tag.match(/^v?(\d+)\.(\d+)\.(\d+)/);
if (!match) return null;
return {
tag,
major: parseInt(match[1], 10),
minor: parseInt(match[2], 10),
patch: parseInt(match[3], 10)
};
};
const sortedReleases = releases
.map(r => parseSemver(r.tag_name))
.filter(v => v !== null)
.sort((a, b) => {
if (a.major !== b.major) return b.major - a.major;
if (a.minor !== b.minor) return b.minor - a.minor;
return b.patch - a.patch;
});
if (sortedReleases.length === 0) {
core.setFailed('No existing releases found. Cannot determine base version for incrementing. Please create an initial release manually (e.g., v0.1.0).');
return;
}
const latestTag = sortedReleases[0].tag;
console.log(`Latest release tag (semver-sorted): ${latestTag}`);
// Parse version components (strip 'v' prefix)
const version = latestTag.replace(/^v/, '');
let [major, minor, patch] = version.split('.').map(Number);
// Increment based on release type
switch (releaseType) {
case 'major':
major += 1;
minor = 0;
patch = 0;
break;
case 'minor':
minor += 1;
patch = 0;
break;
case 'patch':
patch += 1;
break;
}
const releaseTag = `v${major}.${minor}.${patch}`;
console.log(`Computed release tag: ${releaseTag}`);
// Sanity check: Verify the computed tag doesn't already exist
const existingRelease = releases.find(r => r.tag_name === releaseTag);
if (existingRelease) {
core.setFailed(`Release tag ${releaseTag} already exists (created ${existingRelease.created_at}). Cannot create duplicate release. Please check existing releases.`);
return;
}
// Also check if tag exists in git (in case release was deleted but tag remains)
try {
await github.rest.git.getRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `tags/${releaseTag}`
});
// If we get here, the tag exists
core.setFailed(`Git tag ${releaseTag} already exists in the repository. Cannot create duplicate tag. Please delete the existing tag or use a different version.`);
return;
} catch (error) {
// 404 means tag doesn't exist, which is what we want
if (error.status !== 404) {
throw error; // Re-throw unexpected errors
}
}
core.setOutput('release_tag', releaseTag);
console.log(`✓ Release tag: ${releaseTag}`);
echo "## Manual Sync Actions Required" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "The following manual steps must be completed in **github/gh-aw-actions** before this release continues:" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "1. Trigger the **sync-actions** workflow in github/gh-aw-actions:" >> "$GITHUB_STEP_SUMMARY"
echo " https://github.com/github/gh-aw-actions/actions/workflows/sync-actions.yml" >> "$GITHUB_STEP_SUMMARY"
echo "2. Merge the PR created by the sync-actions workflow in **github/gh-aw-actions**" >> "$GITHUB_STEP_SUMMARY"
echo "3. Verify that tag **\`${RELEASE_TAG}\`** exists in github/gh-aw-actions" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Once the above steps are complete, approve the **gh-aw-actions-release** environment gate to continue the release." >> "$GITHUB_STEP_SUMMARY"
echo "Sync actions instructions written for release: $RELEASE_TAG"
echo "Ensure the sync-actions job has been run and the PR merged in github/gh-aw-actions before approving."
echo "Verifying tag $RELEASE_TAG exists in github/gh-aw-actions..."
if gh api "repos/github/gh-aw-actions/git/refs/tags/$RELEASE_TAG" --jq '.ref' > /dev/null 2>&1; then
echo "✓ Tag $RELEASE_TAG exists in github/gh-aw-actions"
else
echo "Error: Tag $RELEASE_TAG not found in github/gh-aw-actions after sync"
exit 1
fi
echo "Auditing SBOM files for potential secrets..."
if grep -rE "GITHUB_TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY" sbom.*.json; then
echo "Error: Potential secrets found in SBOM files"
exit 1
fi
echo "✓ No secrets detected in SBOM files"
set -e
mkdir -p /tmp/gh-aw/release-data
# Use the release ID and tag from the release job
echo "Release ID from release job: $RELEASE_ID"
echo "Release tag from release job: $RELEASE_TAG"
echo "Processing release: $RELEASE_TAG"
echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV"
# Get the current release information
# Use release ID to fetch release data
gh api "/repos/${{ github.repository }}/releases/$RELEASE_ID" > /tmp/gh-aw/release-data/current_release.json
echo "✓ Fetched current release information"
# Get the previous release to determine the range
PREV_RELEASE_TAG=$(gh release list --limit 2 --json tagName --jq '.[1].tagName // empty')
if [ -z "$PREV_RELEASE_TAG" ]; then
echo "No previous release found. This appears to be the first release."
echo "PREV_RELEASE_TAG=" >> "$GITHUB_ENV"
touch /tmp/gh-aw/release-data/pull_requests.json
echo "[]" > /tmp/gh-aw/release-data/pull_requests.json
else
echo "Previous release: $PREV_RELEASE_TAG"
echo "PREV_RELEASE_TAG=$PREV_RELEASE_TAG" >> "$GITHUB_ENV"
# Get commits between releases
echo "Fetching commits between $PREV_RELEASE_TAG and $RELEASE_TAG..."
git fetch --unshallow 2>/dev/null || git fetch --depth=1000
# Get all merged PRs between the two releases (include closingIssuesReferences for attribution)
echo "Fetching pull requests merged between releases..."
PREV_PUBLISHED_AT=$(gh release view "$PREV_RELEASE_TAG" --json publishedAt --jq .publishedAt)
CURR_PUBLISHED_AT=$(gh release view "$RELEASE_TAG" --json publishedAt --jq .publishedAt)
gh pr list \
--state merged \
--limit 1000 \
--json number,title,author,labels,mergedAt,url,body,closingIssuesReferences \
--jq "[.[] | select(.mergedAt >= \"$PREV_PUBLISHED_AT\" and .mergedAt <= \"$CURR_PUBLISHED_AT\")]" \
> /tmp/gh-aw/release-data/pull_requests.json
PR_COUNT=$(jq length "/tmp/gh-aw/release-data/pull_requests.json")
echo "✓ Fetched $PR_COUNT pull requests"
fi
# Build closing references index from GitHub-native closingIssuesReferences
# Maps each closed issue number -> list of PR numbers that directly close it
echo "Building closing references index from GitHub-native PR links..."
# Use a nested reduce so the outer body always returns the accumulator,
# even when closingIssuesReferences is empty (avoids jq setting acc to null).
jq '
reduce .[] as $pr (
{};
reduce ($pr.closingIssuesReferences // [])[] as $issue (
.;
($issue.number | tostring) as $key |
.[$key] = (.[$key] // []) + [$pr.number]
)
)
' /tmp/gh-aw/release-data/pull_requests.json \
> /tmp/gh-aw/release-data/closing_refs_by_issue.json 2>/dev/null \
|| echo "{}" > /tmp/gh-aw/release-data/closing_refs_by_issue.json
# Also expose to community-data dir so shared attribution strategy can reference it
cp /tmp/gh-aw/release-data/closing_refs_by_issue.json /tmp/gh-aw/community-data/closing_refs_by_issue.json
cp /tmp/gh-aw/release-data/pull_requests.json /tmp/gh-aw/community-data/pull_requests.json
DIRECT_CLOSE_COUNT=$(jq 'keys | length' /tmp/gh-aw/release-data/closing_refs_by_issue.json)
echo "✓ Found $DIRECT_CLOSE_COUNT issues with GitHub-native closing PR references"
# Find community issues closed during this release window (candidates for attribution review)
if [ -n "$PREV_PUBLISHED_AT" ]; then
jq --arg prev "$PREV_PUBLISHED_AT" --arg curr "$CURR_PUBLISHED_AT" \
'[.[] | select(.closedAt != null and .closedAt >= $prev and .closedAt <= $curr)]' \
/tmp/gh-aw/community-data/community_issues.json \
> /tmp/gh-aw/release-data/community_issues_closed_in_window.json 2>/dev/null \
|| echo "[]" > /tmp/gh-aw/release-data/community_issues_closed_in_window.json
CLOSED_IN_WINDOW=$(jq length /tmp/gh-aw/release-data/community_issues_closed_in_window.json)
echo "✓ Found $CLOSED_IN_WINDOW community issues closed in this release window"
else
echo "[]" > /tmp/gh-aw/release-data/community_issues_closed_in_window.json
fi
# Get the CHANGELOG.md content around this version
if [ -f "CHANGELOG.md" ]; then
cp CHANGELOG.md /tmp/gh-aw/release-data/CHANGELOG.md
echo "✓ Copied CHANGELOG.md for reference"
fi
# List documentation files for linking
find docs -type f -name "*.md" 2>/dev/null > /tmp/gh-aw/release-data/docs_files.txt || echo "No docs directory found"
echo "✓ Setup complete."
echo " Release data: /tmp/gh-aw/release-data/ (current_release.json, pull_requests.json,"
echo " closing_refs_by_issue.json, community_issues_closed_in_window.json,"
echo " CHANGELOG.md (if exists), docs_files.txt)"
echo " Community data: /tmp/gh-aw/community-data/ (community_issues.json,"
echo " closing_refs_by_issue.json, pull_requests.json)"
Release Highlights Generator
Generate an engaging release highlights summary for ${{ github.repository }} release ${RELEASE_TAG}.
pull_requests.json - PRs merged between ${PREV_RELEASE_TAG} and ${RELEASE_TAG} (includes closingIssuesReferences for each PR; empty array if first release)
closing_refs_by_issue.json - Map of {issue_number: [pr_numbers]} built from GitHub-native closing references in merged PRs
community_issues_closed_in_window.json - Community issues whose closedAt falls within this release window (attribution candidates)
CHANGELOG.md - Full changelog for context (if exists)
docs_files.txt - Available documentation files for linking
Community data is pre-fetched in /tmp/gh-aw/community-data/ (by the shared community-attribution step):
community_issues.json - All issues labeled community (issue number, title, author, closedAt, createdAt, url)
closing_refs_by_issue.json - Same closing references index, mirrored for the shared attribution strategy
pull_requests.json - Same PR list, mirrored for the shared attribution strategy
Output Requirements
Create a "🌟 Release Highlights" section that:
Is concise and scannable (users grasp key changes in 30 seconds)
Uses professional, enthusiastic tone (not overly casual)
Focuses on user impact (why changes matter, not just what changed)
Workflow
1. Load Data
# View release metadata
cat /tmp/gh-aw/release-data/current_release.json | jq
# List PRs (empty if first release)
cat /tmp/gh-aw/release-data/pull_requests.json | jq -r '.[] | "- #\(.number): \(.title) by @\(.author.login)"'# List community issues (fetched by shared community-attribution step)
cat /tmp/gh-aw/community-data/community_issues.json | jq -r '.[] | "- #\(.number): \(.title) by @\(.author.login)"'# View GitHub-native closing references (issue -> [PRs])
cat /tmp/gh-aw/release-data/closing_refs_by_issue.json | jq
# List community issues closed in this release window (attribution candidates)
cat /tmp/gh-aw/release-data/community_issues_closed_in_window.json | jq -r '.[] | "- #\(.number): \(.title) by @\(.author.login) (closed: \(.closedAt))"'# Check CHANGELOG context
head -100 /tmp/gh-aw/release-data/CHANGELOG.md 2>/dev/null ||echo"No CHANGELOG"# View available docs
cat /tmp/gh-aw/release-data/docs_files.txt
2. Identify Community Contributions
The community label is the primary attribution signal — apply the
four-tier Community Attribution Strategy from the imported shared component
(shared/community-attribution.md) to attribute all community-labeled issues
that were closed in this release window. Use /tmp/gh-aw/release-data/community_issues_closed_in_window.json
as the set of candidates and /tmp/gh-aw/release-data/closing_refs_by_issue.json
as the attribution index.
3. Categorize & Prioritize
Group PRs by category (omit categories with no items):
🐛 Bug Fixes - Issue resolutions
⚡ Performance - Speed/efficiency improvements
📚 Documentation - Guide/reference updates
⚠️ Breaking Changes - Requires user action (ALWAYS list first if present)
🔧 Internal - Refactoring, dependencies (usually omit from highlights)
4. Write Highlights
Structure:
## 🌟 Release Highlights[1-2 sentence summary of the release theme/focus]### ⚠️ Breaking Changes[If any - list FIRST with migration guidance]### ✨ What's New[Top 3-5 features with user benefit, link docs when relevant]### 🐛 Bug Fixes & Improvements[Notable fixes - focus on user impact]### 📚 Documentation[Only if significant doc additions/improvements]### 🌍 Community Contributions[Only if any community-labeled issues are resolved in this release]
A huge thank you to the community members who reported issues that were resolved in this release:
-**@[author]** for [issue title] ([#number](url))
-_(via follow-up #N)_ — include only when attribution was confirmed through a follow-up issue chain
[One entry per community issue author. Omit this section entirely if no community issues are resolved.]### ⚠️ Attribution Candidates Need Review[Only if Tier 4 found community issues closed in this release window with no confirmed linkage]
The following community issues were closed during this release window but could not be automatically linked to a specific merged PR. Please verify whether they should be credited:
-**@[author]** for [issue title] ([#number](url)) — closed [date], no confirmed PR linkage found
[Omit this section entirely if all closed community issues have confirmed attribution.]---
For complete details, see [CHANGELOG](https://github.com/github/gh-aw/blob/main/CHANGELOG.md).
Writing Guidelines:
Lead with benefits: "GitHub MCP now supports remote mode" not "Added remote mode"
Be specific: "Reduced compilation time by 40%" not "Faster compilation"
Skip internal changes unless they have user impact
Use docs links: [Learn more](https://github.github.com/gh-aw/path/)
Celebrate community contributors: thank each issue author by name with a link to their issue
5. Handle Special Cases
First Release (no ${PREV_RELEASE_TAG}):
## 🎉 First Release
Welcome to the inaugural release! This version includes [core capabilities].
### Key Features[List primary features with brief descriptions]
Maintenance Release (no user-facing changes):
## 🔧 Maintenance Release
Dependency updates and internal improvements to keep things running smoothly.
Output Format
CRITICAL: You MUST call the update_release MCP tool to update the release with the generated highlights.
HOW TO CALL THE TOOL:
The update_release tool is an MCP (Model Context Protocol) tool, not a bash command or file operation.
Use any file operations - the MCP tool handles everything
Required Parameters:
tag - Release tag from ${RELEASE_TAG} environment variable (e.g., "v0.38.1")
operation - Must be "prepend" to add before existing notes
body - Complete markdown content (include all formatting, emojis, links)
IMPORTANT: The tool is accessed via the MCP gateway as safeoutputs/update_release. When you call this tool, the MCP server automatically writes to /opt/gh-aw/safeoutputs/outputs.jsonl.
WARNING: If you don't call the MCP tool properly, the release notes will NOT be updated!
Verify paths exist in docs_files.txt before linking.
Important: If no action is needed after completing your analysis, you MUST call the noop safe-output tool with a brief explanation. Failing to call any safe-output tool is the most common cause of safe-output workflow failures.
{"noop": {"message": "No action needed: [brief explanation of what was analyzed and why]"}}