From d35af1711e57646025fe50629adc153288f664a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:38:19 +0000 Subject: [PATCH 01/11] Initial plan From e360cee6b666ccc19a738bdce90007b3deecd2be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 02:39:50 +0000 Subject: [PATCH 02/11] ci: add race-safe push logic to changelog workflow Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index 16d4a73..3fee3b4 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -135,15 +135,31 @@ jobs: - name: Commit and push changes if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' run: | + set -euo pipefail BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}" git add CHANGELOG.md + + # Avoid failing when there's nothing new to commit + if git diff --cached --quiet; then + echo "No changelog changes to commit." + exit 0 + fi + git commit -m "docs: update changelog for PR #${{ steps.update-changelog.outputs.pr-number }}" - if [ "${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" = "true" ]; then - git push origin $BRANCH_NAME - else - git push -u origin $BRANCH_NAME + # Always sync latest remote branch tip before push + git fetch origin "$BRANCH_NAME" || true + if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then + git rebase "origin/$BRANCH_NAME" + fi + + # Push and recover from race by rebasing once and retrying + if ! git push origin "$BRANCH_NAME"; then + echo "Push rejected, retrying after rebase..." + git fetch origin "$BRANCH_NAME" + git rebase "origin/$BRANCH_NAME" + git push origin "$BRANCH_NAME" fi - name: Create or update changelog PR From f0113af8e5e32b8dadadd03c7412c8ca6bda16b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 22:29:15 +0000 Subject: [PATCH 03/11] ci: hardcode BASE_BRANCH to master in changelog workflow Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index 3fee3b4..89d1f45 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -56,7 +56,7 @@ jobs: if: steps.check-exclusions.outputs.should-skip == 'false' id: check-existing-changelog-pr run: | - BASE_BRANCH="${{ github.event.pull_request.base.ref }}" + BASE_BRANCH="master" # Find existing changelog PR CHANGELOG_PR=$(gh pr list --base "$BASE_BRANCH" --state open --json number,title,headRefName,body --jq '.[] | select(.title | test("^docs: Update changelog"; "i"))') @@ -165,7 +165,7 @@ jobs: - name: Create or update changelog PR if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' run: | - BASE_BRANCH="${{ github.event.pull_request.base.ref }}" + BASE_BRANCH="master" BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}" PR_NUMBER="${{ steps.update-changelog.outputs.pr-number }}" CHANGELOG_PR_EXISTS="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" From 83412e9628495b7ff583eb6a36c988a9d8e45b1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 22:47:53 +0000 Subject: [PATCH 04/11] ci: introduce CHANGELOG_BASE_BRANCH env var as single source of truth Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index 89d1f45..a62403c 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -10,6 +10,9 @@ permissions: pull-requests: write actions: read +env: + CHANGELOG_BASE_BRANCH: master + jobs: update_changelog: name: Update Changelog @@ -56,7 +59,7 @@ jobs: if: steps.check-exclusions.outputs.should-skip == 'false' id: check-existing-changelog-pr run: | - BASE_BRANCH="master" + BASE_BRANCH="${{ env.CHANGELOG_BASE_BRANCH }}" # Find existing changelog PR CHANGELOG_PR=$(gh pr list --base "$BASE_BRANCH" --state open --json number,title,headRefName,body --jq '.[] | select(.title | test("^docs: Update changelog"; "i"))') @@ -165,7 +168,7 @@ jobs: - name: Create or update changelog PR if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' run: | - BASE_BRANCH="master" + BASE_BRANCH="${{ env.CHANGELOG_BASE_BRANCH }}" BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}" PR_NUMBER="${{ steps.update-changelog.outputs.pr-number }}" CHANGELOG_PR_EXISTS="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" From 0b1fca5d42e5cd325e5b29d1cb66afd24f6941f9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Aug 2026 23:35:55 +0000 Subject: [PATCH 05/11] ci: replace git pull with git reset --hard in checkout changelog branch step Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index a62403c..63ea211 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -104,7 +104,7 @@ jobs: echo "🔄 Checking out existing branch: $BRANCH_NAME" git fetch origin $BRANCH_NAME git checkout $BRANCH_NAME - git pull origin $BRANCH_NAME + git reset --hard origin/$BRANCH_NAME else echo "✨ Creating new branch: $BRANCH_NAME" git checkout -b $BRANCH_NAME From 02288accadc4014b7f569906430b4b54699dbde1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 00:08:23 +0000 Subject: [PATCH 06/11] ci: add workflow_dispatch trigger with PR number input Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 49 ++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index 63ea211..9245ec6 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -4,6 +4,12 @@ on: pull_request: types: - closed + workflow_dispatch: + inputs: + pr_number: + description: "PR number to add to the changelog (must be a merged PR)" + required: true + type: string permissions: contents: write @@ -16,7 +22,7 @@ env: jobs: update_changelog: name: Update Changelog - if: github.event.pull_request.merged == true + if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: @@ -28,16 +34,49 @@ jobs: .github/scripts/update-changelog.mjs sparse-checkout-cone-mode: false + - name: Resolve PR data + id: resolve-pr + uses: actions/github-script@v7 + with: + script: | + let pr; + + if (context.eventName === 'workflow_dispatch') { + const prNumber = parseInt(context.payload.inputs.pr_number, 10); + console.log(`🔍 Fetching PR #${prNumber} from API...`); + + const { data } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: prNumber, + }); + + if (!data.merged) { + core.setFailed(`PR #${prNumber} has not been merged. Only merged PRs can be added to the changelog.`); + return; + } + + pr = data; + console.log(`✅ Resolved PR #${prNumber}: ${pr.title}`); + } else { + pr = context.payload.pull_request; + } + + core.setOutput('pr-json', JSON.stringify(pr)); + - name: Check if PR should be excluded id: check-exclusions uses: actions/github-script@v7 with: script: | const { default: checkExclusions } = await import('${{ github.workspace }}/.github/scripts/check-changelog-exclusions.mjs'); + const pr = JSON.parse(process.env.RESOLVED_PR); return await checkExclusions({ - pr: context.payload.pull_request, + pr, core }); + env: + RESOLVED_PR: ${{ steps.resolve-pr.outputs.pr-json }} - name: Changelog update skipped if: steps.check-exclusions.outputs.should-skip == 'true' @@ -117,10 +156,13 @@ jobs: with: script: | const { default: updateChangelog } = await import('/tmp/update-changelog.mjs'); + const pr = JSON.parse(process.env.RESOLVED_PR); return await updateChangelog({ - pr: context.payload.pull_request, + pr, core }); + env: + RESOLVED_PR: ${{ steps.resolve-pr.outputs.pr-json }} - name: Prettify Changelog id: prettify-changelog @@ -200,3 +242,4 @@ jobs: fi env: GH_TOKEN: ${{ github.token }} + From d975f6c9bd2d54423a52a25a2de5f10901dfb86b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:23:35 +0000 Subject: [PATCH 07/11] fix: harden changelog workflow input and push guards Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index 9245ec6..d0d317c 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -42,7 +42,13 @@ jobs: let pr; if (context.eventName === 'workflow_dispatch') { - const prNumber = parseInt(context.payload.inputs.pr_number, 10); + const rawPrNumber = (context.payload.inputs?.pr_number ?? '').trim(); + if (!/^\d+$/.test(rawPrNumber) || Number(rawPrNumber) <= 0) { + core.setFailed(`Invalid workflow input "pr_number": "${rawPrNumber}". Please provide a positive integer PR number.`); + return; + } + + const prNumber = Number(rawPrNumber); console.log(`🔍 Fetching PR #${prNumber} from API...`); const { data } = await github.rest.pulls.get({ @@ -141,12 +147,12 @@ jobs: if [ "${{ steps.check-existing-changelog-pr.outputs.changelog-pr-exists }}" = "true" ]; then echo "🔄 Checking out existing branch: $BRANCH_NAME" - git fetch origin $BRANCH_NAME - git checkout $BRANCH_NAME - git reset --hard origin/$BRANCH_NAME + git fetch origin "$BRANCH_NAME" + git checkout "$BRANCH_NAME" + git reset --hard "origin/$BRANCH_NAME" else echo "✨ Creating new branch: $BRANCH_NAME" - git checkout -b $BRANCH_NAME + git checkout -b "$BRANCH_NAME" fi - name: Update Changelog @@ -178,10 +184,12 @@ jobs: git diff CHANGELOG.md - name: Commit and push changes + id: commit-push if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' run: | set -euo pipefail BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}" + echo "pushed=false" >> "$GITHUB_OUTPUT" git add CHANGELOG.md @@ -194,21 +202,25 @@ jobs: git commit -m "docs: update changelog for PR #${{ steps.update-changelog.outputs.pr-number }}" # Always sync latest remote branch tip before push - git fetch origin "$BRANCH_NAME" || true - if git show-ref --verify --quiet "refs/remotes/origin/$BRANCH_NAME"; then + if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then + git fetch origin "$BRANCH_NAME" git rebase "origin/$BRANCH_NAME" fi # Push and recover from race by rebasing once and retrying if ! git push origin "$BRANCH_NAME"; then echo "Push rejected, retrying after rebase..." - git fetch origin "$BRANCH_NAME" - git rebase "origin/$BRANCH_NAME" + if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then + git fetch origin "$BRANCH_NAME" + git rebase "origin/$BRANCH_NAME" + fi git push origin "$BRANCH_NAME" fi + echo "pushed=true" >> "$GITHUB_OUTPUT" + - name: Create or update changelog PR - if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' + if: steps.check-exclusions.outputs.should-skip == 'false' && steps.update-changelog.outputs.changelog-updated == 'true' && steps.commit-push.outputs.pushed == 'true' run: | BASE_BRANCH="${{ env.CHANGELOG_BASE_BRANCH }}" BRANCH_NAME="${{ steps.check-existing-changelog-pr.outputs.changelog-pr-branch-name }}" @@ -242,4 +254,3 @@ jobs: fi env: GH_TOKEN: ${{ github.token }} - From 7b0fd81e890e536606599033ee40a43eca81fd6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:24:30 +0000 Subject: [PATCH 08/11] fix: improve changelog push retry failure handling Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index d0d317c..e9bcdd7 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -213,8 +213,13 @@ jobs: if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then git fetch origin "$BRANCH_NAME" git rebase "origin/$BRANCH_NAME" + else + echo "Remote branch $BRANCH_NAME not found during retry; retrying push without rebase." + fi + if ! git push origin "$BRANCH_NAME"; then + echo "Failed to push changelog changes after retry." >&2 + exit 1 fi - git push origin "$BRANCH_NAME" fi echo "pushed=true" >> "$GITHUB_OUTPUT" From a6a1cad0b8c4aafe3fbf43bc6676cbde4fbaeb7d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:25:06 +0000 Subject: [PATCH 09/11] fix: simplify changelog push flow and inline PR payload Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index e9bcdd7..beb50b4 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -76,13 +76,11 @@ jobs: with: script: | const { default: checkExclusions } = await import('${{ github.workspace }}/.github/scripts/check-changelog-exclusions.mjs'); - const pr = JSON.parse(process.env.RESOLVED_PR); + const pr = ${{ steps.resolve-pr.outputs.pr-json }}; return await checkExclusions({ pr, core }); - env: - RESOLVED_PR: ${{ steps.resolve-pr.outputs.pr-json }} - name: Changelog update skipped if: steps.check-exclusions.outputs.should-skip == 'true' @@ -162,13 +160,11 @@ jobs: with: script: | const { default: updateChangelog } = await import('/tmp/update-changelog.mjs'); - const pr = JSON.parse(process.env.RESOLVED_PR); + const pr = ${{ steps.resolve-pr.outputs.pr-json }}; return await updateChangelog({ pr, core }); - env: - RESOLVED_PR: ${{ steps.resolve-pr.outputs.pr-json }} - name: Prettify Changelog id: prettify-changelog @@ -201,18 +197,16 @@ jobs: git commit -m "docs: update changelog for PR #${{ steps.update-changelog.outputs.pr-number }}" - # Always sync latest remote branch tip before push - if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then - git fetch origin "$BRANCH_NAME" - git rebase "origin/$BRANCH_NAME" - fi - # Push and recover from race by rebasing once and retrying if ! git push origin "$BRANCH_NAME"; then echo "Push rejected, retrying after rebase..." if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then git fetch origin "$BRANCH_NAME" - git rebase "origin/$BRANCH_NAME" + if ! git rebase "origin/$BRANCH_NAME"; then + echo "Rebase failed while retrying push to $BRANCH_NAME." >&2 + git rebase --abort || true + exit 1 + fi else echo "Remote branch $BRANCH_NAME not found during retry; retrying push without rebase." fi From fe43dcf8961f51f1a215e4cb502334f6ad732d9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:25:41 +0000 Subject: [PATCH 10/11] fix: safely pass PR payload through workflow steps Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index beb50b4..b474087 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -68,7 +68,9 @@ jobs: pr = context.payload.pull_request; } - core.setOutput('pr-json', JSON.stringify(pr)); + const prJson = JSON.stringify(pr); + core.setOutput('pr-json', prJson); + core.setOutput('pr-json-base64', Buffer.from(prJson, 'utf8').toString('base64')); - name: Check if PR should be excluded id: check-exclusions @@ -76,11 +78,13 @@ jobs: with: script: | const { default: checkExclusions } = await import('${{ github.workspace }}/.github/scripts/check-changelog-exclusions.mjs'); - const pr = ${{ steps.resolve-pr.outputs.pr-json }}; + const pr = JSON.parse(Buffer.from(process.env.RESOLVED_PR_BASE64, 'base64').toString('utf8')); return await checkExclusions({ pr, core }); + env: + RESOLVED_PR_BASE64: ${{ steps.resolve-pr.outputs.pr-json-base64 }} - name: Changelog update skipped if: steps.check-exclusions.outputs.should-skip == 'true' @@ -160,11 +164,13 @@ jobs: with: script: | const { default: updateChangelog } = await import('/tmp/update-changelog.mjs'); - const pr = ${{ steps.resolve-pr.outputs.pr-json }}; + const pr = JSON.parse(Buffer.from(process.env.RESOLVED_PR_BASE64, 'base64').toString('utf8')); return await updateChangelog({ pr, core }); + env: + RESOLVED_PR_BASE64: ${{ steps.resolve-pr.outputs.pr-json-base64 }} - name: Prettify Changelog id: prettify-changelog From 3de39e2c0b8ad2bb3a5b924911a1aee53c61169a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 7 Aug 2026 01:26:20 +0000 Subject: [PATCH 11/11] fix: quote workflow payload env and set push upstream Co-authored-by: yCodeTech <31927084+yCodeTech@users.noreply.github.com> --- .github/workflows/changelog-ci.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/changelog-ci.yml b/.github/workflows/changelog-ci.yml index b474087..6c2057b 100644 --- a/.github/workflows/changelog-ci.yml +++ b/.github/workflows/changelog-ci.yml @@ -69,7 +69,6 @@ jobs: } const prJson = JSON.stringify(pr); - core.setOutput('pr-json', prJson); core.setOutput('pr-json-base64', Buffer.from(prJson, 'utf8').toString('base64')); - name: Check if PR should be excluded @@ -84,7 +83,7 @@ jobs: core }); env: - RESOLVED_PR_BASE64: ${{ steps.resolve-pr.outputs.pr-json-base64 }} + RESOLVED_PR_BASE64: "${{ steps.resolve-pr.outputs.pr-json-base64 }}" - name: Changelog update skipped if: steps.check-exclusions.outputs.should-skip == 'true' @@ -170,7 +169,7 @@ jobs: core }); env: - RESOLVED_PR_BASE64: ${{ steps.resolve-pr.outputs.pr-json-base64 }} + RESOLVED_PR_BASE64: "${{ steps.resolve-pr.outputs.pr-json-base64 }}" - name: Prettify Changelog id: prettify-changelog @@ -204,7 +203,7 @@ jobs: git commit -m "docs: update changelog for PR #${{ steps.update-changelog.outputs.pr-number }}" # Push and recover from race by rebasing once and retrying - if ! git push origin "$BRANCH_NAME"; then + if ! git push -u origin "$BRANCH_NAME"; then echo "Push rejected, retrying after rebase..." if git ls-remote --exit-code --heads origin "$BRANCH_NAME" >/dev/null 2>&1; then git fetch origin "$BRANCH_NAME" @@ -216,7 +215,7 @@ jobs: else echo "Remote branch $BRANCH_NAME not found during retry; retrying push without rebase." fi - if ! git push origin "$BRANCH_NAME"; then + if ! git push -u origin "$BRANCH_NAME"; then echo "Failed to push changelog changes after retry." >&2 exit 1 fi