Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 68 additions & 6 deletions .github/workflows/enhance-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
types: [opened, synchronize, reopened]
branches:
- main
workflow_dispatch: # Added to allow manual triggering
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

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

Adding workflow_dispatch trigger without handling the case when github.event.pull_request is null will cause failures. When manually triggered, github.event.pull_request.number (line 12), github.event.pull_request.head.ref (lines 24, 30, 145, 319), github.event.pull_request.title (line 38), and github.event.pull_request.base.ref (lines 58-59) will all be undefined, causing the workflow to fail. Either add inputs to workflow_dispatch to provide these values, or guard the job execution with a condition that checks if the trigger is pull_request.

Copilot uses AI. Check for mistakes.

# Cancel in-progress runs when a new commit is pushed to the same PR
concurrency:
Expand All @@ -20,20 +21,21 @@ permissions:
jobs:
enhance-release-pr:
name: 🤖 Claude AI Enhancement
if: startsWith(github.head_ref, 'release-please--')
if: startsWith(github.event.pull_request.head.ref, 'release-please--')
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout PR Branch
uses: actions/checkout@v5
with:
ref: ${{ github.head_ref }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: 🏷️ Extract Version from PR Title
id: version
run: |
VERSION=$(echo "${{ github.event.pull_request.title }}" | grep -oP '\d+\.\d+\.\d+' || echo "")
# Extract version using sed -E (extended regex, more portable than grep -P)
VERSION=$(echo "${{ github.event.pull_request.title }}" | sed -E 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/' || echo "")
Comment on lines 34 to +38

Choose a reason for hiding this comment

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

P1 Badge Version parsing succeeds on non‑semver PR titles

The new sed-based extraction returns the original PR title when no X.Y.Z match is found, so VERSION is non-empty even for titles without a semantic version. That flips skip to false and drives the rest of the workflow with an arbitrary string instead of skipping as before (e.g., if the release PR is retitled or manual dispatch lacks a version).

Useful? React with 👍 / 👎.

Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

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

The sed pattern replacement may not work as intended. Unlike grep -oP which only outputs matching text, sed 's/.../.../ will output the entire line unchanged if the pattern doesn't match. If the PR title doesn't contain a version number, VERSION will be set to the entire title instead of being empty. Consider using: sed -n 's/.*\([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p' with the -n flag and p command to only print when there's a match, or add a pattern test first.

Suggested change
VERSION=$(echo "${{ github.event.pull_request.title }}" | sed -E 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/' || echo "")
VERSION=$(echo "${{ github.event.pull_request.title }}" | sed -n -E 's/.*([0-9]+\.[0-9]+\.[0-9]+).*/\1/p')

Copilot uses AI. Check for mistakes.
if [ -z "$VERSION" ]; then
echo "⚠️ Could not extract version from PR title"
echo "skip=true" >> $GITHUB_OUTPUT
Expand All @@ -46,8 +48,68 @@ jobs:
- name: ⬅️ Get Previous Version
id: prev_version
if: steps.version.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
PREV=$(cat .release-please-manifest.json | grep -oP '"\.: "\K[^"]+' || echo "0.0.0")
echo "🔍 Determining previous version..."

# Method 1: Try to get from base branch's manifest (most reliable)
# Uses GitHub API to read .release-please-manifest.json from the base branch
echo "📋 Checking base branch (${{ github.event.pull_request.base.ref }}) manifest..."
BASE_MANIFEST=$(gh api repos/${{ github.repository }}/contents/.release-please-manifest.json?ref=${{ github.event.pull_request.base.ref }} 2>/dev/null | jq -r '.content' | base64 -d 2>/dev/null || echo "")
if [ -n "$BASE_MANIFEST" ]; then
PREV=$(echo "$BASE_MANIFEST" | jq -r '."."' 2>/dev/null || echo "")
if [ -n "$PREV" ] && [ "$PREV" != "null" ] && [ "$PREV" != "0.0.0" ]; then
echo "✅ Found version from base branch manifest: $PREV"
echo "prev_version=$PREV" >> $GITHUB_OUTPUT
echo "📦 Previous version: $PREV"
exit 0
fi
fi

# Method 2: Get latest GitHub release tag
echo "🏷️ Checking GitHub releases..."
LATEST_RELEASE=$(gh api repos/${{ github.repository }}/releases/latest 2>/dev/null | jq -r '.tag_name' | sed 's/^v//' || echo "")
if [ -n "$LATEST_RELEASE" ] && [ "$LATEST_RELEASE" != "null" ]; then
echo "✅ Found version from GitHub releases: $LATEST_RELEASE"
echo "prev_version=$LATEST_RELEASE" >> $GITHUB_OUTPUT
echo "📦 Previous version: $LATEST_RELEASE"
exit 0
fi

# Method 3: Get latest tag (even if no release exists)
echo "🏷️ Checking Git tags..."
# Use sed instead of grep -oP for portability (BSD grep doesn't support -P)
LATEST_TAG=$(git ls-remote --tags origin | sed -nE 's|.*refs/tags/v([0-9]+\.[0-9]+\.[0-9]+).*|\1|p' | sort -V | tail -1 || echo "")
if [ -n "$LATEST_TAG" ]; then
echo "✅ Found version from Git tags: $LATEST_TAG"
echo "prev_version=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "📦 Previous version: $LATEST_TAG"
exit 0
fi

# Method 4: Fallback to PR branch manifest (might have new version, but better than 0.0.0)
echo "📋 Checking PR branch manifest (fallback)..."
PREV=$(jq -r '."."' .release-please-manifest.json 2>/dev/null || echo "0.0.0")
if [ "$PREV" = "null" ] || [ -z "$PREV" ]; then
PREV="0.0.0"
fi

# If we got the same version as proposed, try to decrement patch version
if [ "$PREV" = "${{ steps.version.outputs.version }}" ]; then
echo "⚠️ PR branch has same version as proposed, attempting to infer previous..."
# Try to get from git log or fallback to decrementing patch
MAJOR=$(echo "$PREV" | cut -d. -f1)
MINOR=$(echo "$PREV" | cut -d. -f2)
PATCH=$(echo "$PREV" | cut -d. -f3)
if [ "$PATCH" -gt 0 ]; then
PREV="$MAJOR.$MINOR.$((PATCH - 1))"
echo "📉 Decremented patch version: $PREV"
else
PREV="0.0.0"
fi
fi

echo "prev_version=$PREV" >> $GITHUB_OUTPUT
echo "📦 Previous version: $PREV"

Expand Down Expand Up @@ -80,7 +142,7 @@ jobs:
REPO: ${{ github.repository }}
PROPOSED_VERSION: ${{ steps.version.outputs.version }}
PREVIOUS_VERSION: ${{ steps.prev_version.outputs.prev_version }}
PR_BRANCH: ${{ github.head_ref }}
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
PR_NUMBER: ${{ github.event.pull_request.number }}

# 🤖 CLAUDE CODE: RELEASE MANAGER FOR open-runtime/grpc-dart
Expand Down Expand Up @@ -254,7 +316,7 @@ jobs:

3. **Push All Changes:**
```bash
git push origin ${{ github.head_ref }}
git push origin ${{ github.event.pull_request.head.ref }}
```

4. **Be Conservative** - this is a critical infrastructure package
Expand Down
133 changes: 75 additions & 58 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,54 +32,19 @@ permissions:
actions: write

jobs:
release-please:
name: 📦 Release Please
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
releases_created: ${{ steps.release.outputs.releases_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
major: ${{ steps.release.outputs.major }}
minor: ${{ steps.release.outputs.minor }}
patch: ${{ steps.release.outputs.patch }}
sha: ${{ steps.release.outputs.sha }}
pr: ${{ steps.release.outputs.pr }}
prs_created: ${{ steps.release.outputs.prs_created }}
steps:
- name: 📥 Checkout source code
uses: actions/checkout@v4

- name: 🤖 Run Release Please
id: release
uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
target-branch: ${{ github.ref_name }}

- name: 📝 Log Release Info
if: ${{ steps.release.outputs.releases_created == 'true' }}
run: |
echo "🎉 Release created!"
echo "📦 Version: ${{ steps.release.outputs.version }}"
echo "🏷️ Tag: ${{ steps.release.outputs.tag_name }}"
echo "🔗 https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }}"

# =============================================================================
# Fallback: Handle Untagged Merged Release PRs
# =============================================================================
# If Release Please aborts due to "untagged merged release PRs", this job
# will find those PRs, create the missing tags, and create GitHub releases.
# This handles edge cases like when Claude changes the version in a Release PR.
# Step 1: Handle Untagged Merged Release PRs FIRST
# =============================================================================
# This MUST run before release-please to prevent "untagged PRs outstanding" abort
# Release Please aborts if it finds merged PRs with "autorelease: pending" label
# By running this first, we ensure all untagged PRs are tagged before Release Please checks
handle-untagged-releases:
name: 🔧 Handle Untagged Releases (Fallback)
needs: release-please
# Run if Release Please didn't create a release (might have aborted)
if: ${{ needs.release-please.outputs.releases_created != 'true' }}
name: 🔧 Handle Untagged Releases (Pre-Check)
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.process.outputs.release_created }}
tag_name: ${{ steps.process.outputs.tag_name }}
version: ${{ steps.process.outputs.version }}
Comment on lines +44 to +47

Choose a reason for hiding this comment

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

P1 Badge handle-untagged-releases outputs never set

The job exports release_created, tag_name, and version from steps.process, but no step in the job has id: process. As a result those outputs are always empty, so downstream verify-release never runs when this job creates a tag/release and cannot check the produced artifact.

Useful? React with 👍 / 👎.

Comment on lines +45 to +47
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

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

The job outputs reference steps.process.outputs.*, but there is no step with id: process in this job. The step at line 54 ("🔍 Find and process untagged merged release PRs") needs id: process added so these outputs can be properly referenced.

Copilot uses AI. Check for mistakes.
steps:
- name: 📥 Checkout source code
uses: actions/checkout@v4
Expand Down Expand Up @@ -127,7 +92,6 @@ jobs:
echo " SHA: $MERGE_SHA"
echo " Tag: $TAG_NAME"

# Check if tag already exists
if gh api repos/${{ github.repository }}/git/refs/tags/$TAG_NAME 2>/dev/null; then
echo "✅ Tag $TAG_NAME already exists"
else
Expand All @@ -138,13 +102,14 @@ jobs:
-f sha="$MERGE_SHA"
fi

# Check if release already exists
if gh release view $TAG_NAME --repo ${{ github.repository }} 2>/dev/null; then
echo "✅ Release $TAG_NAME already exists"
else
echo "🎉 Creating GitHub Release $TAG_NAME"
RELEASE_CREATED=true
LATEST_TAG="$TAG_NAME"
LATEST_VERSION="$VERSION"

# Extract changelog for this version
CHANGELOG=$(awk -v ver="$VERSION" '
/^## \[/ {
if (found) exit
Expand All @@ -157,7 +122,6 @@ jobs:
CHANGELOG="Release v$VERSION - See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md) for details."
fi

# Create release notes file using printf (heredocs break YAML indentation)
{
echo "## 📦 Installation"
echo ""
Expand Down Expand Up @@ -185,36 +149,88 @@ jobs:
echo "$CHANGELOG"
} > /tmp/release_notes.md

# Create the release
gh release create $TAG_NAME \
--repo ${{ github.repository }} \
--title "v$VERSION" \
--notes-file /tmp/release_notes.md
fi

# Update label to autorelease: tagged
echo "🏷️ Updating PR #$PR_NUM label to autorelease: tagged"
gh pr edit $PR_NUM \
--repo ${{ github.repository }} \
--remove-label "autorelease: pending" \
--add-label "autorelease: tagged" || true

echo "✅ Processed PR #$PR_NUM"
done
done < <(echo "$PENDING_PRS" | jq -c '.')
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

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

Conflicting input redirection for the while loop. The loop at line 76 already receives input via pipe (echo "$PENDING_PRS" | jq -c '.' | while read -r pr; do), but line 165 attempts to redirect input again with done < <(...). This will cause a syntax error or unexpected behavior. Either use the pipe on line 76 and just use done at line 165, or remove the pipe on line 76 and keep the process substitution at line 165. The process substitution approach (while read -r pr; do ... done < <(...)) is preferred to avoid the subshell issue mentioned in the comment for lines 109-111.

Copilot uses AI. Check for mistakes.

# Output the latest release info (if any was created)
if [ "$RELEASE_CREATED" = "true" ]; then
echo "release_created=true" >> $GITHUB_OUTPUT
echo "tag_name=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT
else
echo "release_created=false" >> $GITHUB_OUTPUT
fi

# =============================================================================
# Step 2: Run Release Please (after untagged PRs are handled)
# =============================================================================
release-please:
name: 📦 Release Please
needs: handle-untagged-releases
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
releases_created: ${{ steps.release.outputs.releases_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
version: ${{ steps.release.outputs.version }}
major: ${{ steps.release.outputs.major }}
minor: ${{ steps.release.outputs.minor }}
patch: ${{ steps.release.outputs.patch }}
sha: ${{ steps.release.outputs.sha }}
pr: ${{ steps.release.outputs.pr }}
prs_created: ${{ steps.release.outputs.prs_created }}
steps:
- name: 📥 Checkout source code
uses: actions/checkout@v4

- name: 🤖 Run Release Please
id: release
uses: googleapis/release-please-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
target-branch: ${{ github.ref_name }}

- name: 📝 Log Release Info
if: ${{ steps.release.outputs.releases_created == 'true' }}
run: |
echo "🎉 Release created!"
echo "📦 Version: ${{ steps.release.outputs.version }}"
echo "🏷️ Tag: ${{ steps.release.outputs.tag_name }}"
echo "🔗 https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }}"

# =============================================================================
# Step 3: Verify Release
# =============================================================================

# =============================================================================
# Verify Release
# =============================================================================
Comment on lines 219 to 221
Copy link

Copilot AI Nov 30, 2025

Choose a reason for hiding this comment

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

Duplicate comment headers. Lines 215-217 say "Step 3: Verify Release" and lines 219-221 repeat "Verify Release". Remove one set of these duplicate headers.

Suggested change
# =============================================================================
# Verify Release
# =============================================================================

Copilot uses AI. Check for mistakes.
verify-release:
name: ✅ Verify Release
needs: release-please
if: ${{ needs.release-please.outputs.releases_created == 'true' }}
needs: [release-please, handle-untagged-releases]
if: |
needs.release-please.outputs.releases_created == 'true' ||
(needs.handle-untagged-releases.outputs.release_created == 'true' && needs.handle-untagged-releases.result == 'success')
runs-on: ubuntu-latest
steps:
- name: 📥 Checkout source code
uses: actions/checkout@v4
with:
ref: ${{ needs.release-please.outputs.tag_name }}
ref: ${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.tag_name || needs.handle-untagged-releases.outputs.tag_name }}

- name: 🎯 Install Dart SDK
uses: dart-lang/setup-dart@v1
Expand All @@ -228,7 +244,7 @@ jobs:
run: |
echo "📦 Verifying package version..."
VERSION=$(grep '^version:' pubspec.yaml | awk '{print $2}')
EXPECTED="${{ needs.release-please.outputs.version }}"
EXPECTED="${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.version || needs.handle-untagged-releases.outputs.version }}"
if [ "$VERSION" != "$EXPECTED" ]; then
echo "❌ Version mismatch: pubspec.yaml has $VERSION, expected $EXPECTED"
exit 1
Expand All @@ -243,7 +259,8 @@ jobs:

- name: 📋 Release Summary
run: |
echo "## 🎉 Release v${{ needs.release-please.outputs.version }} Verified!" >> $GITHUB_STEP_SUMMARY
VERSION="${{ needs.release-please.outputs.releases_created == 'true' && needs.release-please.outputs.version || needs.handle-untagged-releases.outputs.version }}"
echo "## 🎉 Release v$VERSION Verified!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📦 Installation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
Expand All @@ -254,9 +271,9 @@ jobs:
echo " git:" >> $GITHUB_STEP_SUMMARY
echo " url: https://github.com/open-runtime/grpc-dart" >> $GITHUB_STEP_SUMMARY
echo " tag_pattern: \"^v\"" >> $GITHUB_STEP_SUMMARY
echo " version: ^${{ needs.release-please.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo " version: ^$VERSION" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🔗 Links" >> $GITHUB_STEP_SUMMARY
echo "- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/v${{ needs.release-please.outputs.version }})" >> $GITHUB_STEP_SUMMARY
echo "- [GitHub Release](https://github.com/${{ github.repository }}/releases/tag/v$VERSION)" >> $GITHUB_STEP_SUMMARY
echo "- [Changelog](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md)" >> $GITHUB_STEP_SUMMARY