From fd4d17ac5431104f32e48e7df3ccc60c418a6865 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 12:18:01 +0000
Subject: [PATCH 1/4] feat: add update-stable-release workflow

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/34c8400f-ce29-4429-a454-b91614f2473f

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .github/workflows/update-stable-release.yml | 134 ++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100644 .github/workflows/update-stable-release.yml

diff --git a/.github/workflows/update-stable-release.yml b/.github/workflows/update-stable-release.yml
new file mode 100644
index 00000000000..906370c18a3
--- /dev/null
+++ b/.github/workflows/update-stable-release.yml
@@ -0,0 +1,134 @@
+name: Update Stable Release
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: "Version to set as stable (e.g., v1.2.3). Defaults to the latest published release."
+        required: false
+        type: string
+
+# Prevent concurrent runs that could produce conflicting commits
+concurrency:
+  group: update-stable-release
+  cancel-in-progress: false
+
+jobs:
+  update-stable:
+    name: Update Stable Release
+    runs-on: ubuntu-latest
+    # Never run on forks — they cannot push to the upstream repository
+    if: ${{ !github.event.repository.fork }}
+    permissions:
+      contents: write
+
+    steps:
+      - name: Validate actor permissions
+        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+        with:
+          script: |
+            const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              username: context.actor,
+            });
+            const role = data.role_name;
+            core.info(`Actor ${context.actor} has role: ${role}`);
+            if (role !== 'admin' && role !== 'maintain') {
+              core.setFailed(
+                `❌ ${context.actor} must be a repository admin or maintainer to update the stable release (current role: ${role})`
+              );
+            } else {
+              core.info(`✅ ${context.actor} is authorized (role: ${role})`);
+            }
+
+      - name: Resolve version
+        id: version
+        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+        with:
+          script: |
+            let version = '${{ inputs.version }}'.trim();
+            if (!version) {
+              const { data: release } = await github.rest.repos.getLatestRelease({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+              });
+              version = release.tag_name;
+              core.info(`ℹ️ No version specified, resolved to latest: ${version}`);
+            }
+            core.setOutput('version', version);
+
+      - name: Validate release tag
+        uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+        with:
+          script: |
+            const version = '${{ steps.version.outputs.version }}';
+
+            // Only exact vMAJOR.MINOR.PATCH versions are valid for the stable alias
+            // (pre-release suffixes such as -beta.1 are intentionally rejected)
+            if (!/^v\d+\.\d+\.\d+$/.test(version)) {
+              core.setFailed(
+                `❌ Invalid version format: "${version}". Expected vMAJOR.MINOR.PATCH (e.g., v1.2.3). ` +
+                `Pre-release versions cannot be set as stable.`
+              );
+              return;
+            }
+
+            let release;
+            try {
+              ({ data: release } = await github.rest.repos.getReleaseByTag({
+                owner: context.repo.owner,
+                repo: context.repo.repo,
+                tag: version,
+              }));
+            } catch (err) {
+              core.setFailed(`❌ No published release found for tag ${version}: ${err.message}`);
+              return;
+            }
+
+            if (release.draft) {
+              core.setFailed(`❌ ${version} is a draft release and cannot be set as stable`);
+              return;
+            }
+
+            if (release.prerelease) {
+              core.warning(`⚠️ ${version} is marked as a pre-release`);
+            }
+
+            core.info(`✅ Validated: ${version} is a published release`);
+
+      - name: Checkout repository
+        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+      - name: Update stable alias in releases.json
+        id: update
+        run: |
+          VERSION="${{ steps.version.outputs.version }}"
+          FILE=".github/aw/releases.json"
+
+          CURRENT=$(jq -r '.aliases.stable // ""' "$FILE")
+
+          if [ -z "$CURRENT" ]; then
+            echo "ℹ️ No existing stable alias found; will create one."
+          fi
+
+          if [ "$CURRENT" = "$VERSION" ]; then
+            echo "ℹ️ stable is already set to $VERSION — no change needed."
+            echo "changed=false" >> "$GITHUB_OUTPUT"
+          else
+            echo "Updating stable alias: $CURRENT → $VERSION"
+            jq --arg v "$VERSION" '.aliases.stable = $v' "$FILE" > /tmp/releases.json
+            mv /tmp/releases.json "$FILE"
+            echo "✅ Updated $FILE"
+            cat "$FILE"
+            echo "changed=true" >> "$GITHUB_OUTPUT"
+          fi
+
+      - name: Commit and push
+        if: steps.update.outputs.changed == 'true'
+        run: |
+          git config user.name "github-actions[bot]"
+          git config user.email "github-actions[bot]@users.noreply.github.com"
+          git add .github/aw/releases.json
+          git commit -m "chore: update stable release to ${{ steps.version.outputs.version }}"
+          git push

From 06fc209fa1a935884817ec2ff1481cc1f539fcd1 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 12:24:31 +0000
Subject: [PATCH 2/4] feat: rename workflow to Release Punter

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/796051fd-b3b7-46dd-87bf-884bfff4414e

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .github/workflows/update-stable-release.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/update-stable-release.yml b/.github/workflows/update-stable-release.yml
index 906370c18a3..a0bc4613fd0 100644
--- a/.github/workflows/update-stable-release.yml
+++ b/.github/workflows/update-stable-release.yml
@@ -1,4 +1,4 @@
-name: Update Stable Release
+name: Release Punter
 
 on:
   workflow_dispatch:
@@ -15,7 +15,7 @@ concurrency:
 
 jobs:
   update-stable:
-    name: Update Stable Release
+    name: Release Punter
     runs-on: ubuntu-latest
     # Never run on forks — they cannot push to the upstream repository
     if: ${{ !github.event.repository.fork }}

From 19a81d0b5cc6e64e9059ad6a38cae01da09d4977 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 12:34:36 +0000
Subject: [PATCH 3/4] fix: move inline expression expansions to env var
 references to prevent shell injection

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/d184af5e-c5bf-4c67-841a-0b13f898be1e

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .github/workflows/update-stable-release.yml | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/update-stable-release.yml b/.github/workflows/update-stable-release.yml
index a0bc4613fd0..b2dfdf19d03 100644
--- a/.github/workflows/update-stable-release.yml
+++ b/.github/workflows/update-stable-release.yml
@@ -45,9 +45,11 @@ jobs:
       - name: Resolve version
         id: version
         uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+        env:
+          INPUT_VERSION: ${{ inputs.version }}
         with:
           script: |
-            let version = '${{ inputs.version }}'.trim();
+            let version = (process.env.INPUT_VERSION || '').trim();
             if (!version) {
               const { data: release } = await github.rest.repos.getLatestRelease({
                 owner: context.repo.owner,
@@ -60,9 +62,11 @@ jobs:
 
       - name: Validate release tag
         uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+        env:
+          RELEASE_VERSION: ${{ steps.version.outputs.version }}
         with:
           script: |
-            const version = '${{ steps.version.outputs.version }}';
+            const version = process.env.RELEASE_VERSION;
 
             // Only exact vMAJOR.MINOR.PATCH versions are valid for the stable alias
             // (pre-release suffixes such as -beta.1 are intentionally rejected)
@@ -102,8 +106,9 @@ jobs:
 
       - name: Update stable alias in releases.json
         id: update
+        env:
+          VERSION: ${{ steps.version.outputs.version }}
         run: |
-          VERSION="${{ steps.version.outputs.version }}"
           FILE=".github/aw/releases.json"
 
           CURRENT=$(jq -r '.aliases.stable // ""' "$FILE")
@@ -126,9 +131,11 @@ jobs:
 
       - name: Commit and push
         if: steps.update.outputs.changed == 'true'
+        env:
+          VERSION: ${{ steps.version.outputs.version }}
         run: |
           git config user.name "github-actions[bot]"
           git config user.email "github-actions[bot]@users.noreply.github.com"
           git add .github/aw/releases.json
-          git commit -m "chore: update stable release to ${{ steps.version.outputs.version }}"
+          git commit -m "chore: update stable release to $VERSION"
           git push

From ea29e78319343f346cc26ba70f0f1cee347bfbc8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 31 Mar 2026 12:43:11 +0000
Subject: [PATCH 4/4] fix: add JSON validation of releases.json after update

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/fffd70fe-9740-4577-a96e-044a9248c748

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
---
 .github/workflows/update-stable-release.yml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/.github/workflows/update-stable-release.yml b/.github/workflows/update-stable-release.yml
index b2dfdf19d03..6f947c27f62 100644
--- a/.github/workflows/update-stable-release.yml
+++ b/.github/workflows/update-stable-release.yml
@@ -129,6 +129,16 @@ jobs:
             echo "changed=true" >> "$GITHUB_OUTPUT"
           fi
 
+      - name: Validate releases.json
+        if: steps.update.outputs.changed == 'true'
+        run: |
+          FILE=".github/aw/releases.json"
+          if ! jq empty "$FILE" 2>&1; then
+            echo "❌ $FILE is not valid JSON after update"
+            exit 1
+          fi
+          echo "✅ $FILE is valid JSON"
+
       - name: Commit and push
         if: steps.update.outputs.changed == 'true'
         env:
