Skip to content

Commit 4edc3aa

Browse files
jencarlucciCopilot
andcommitted
Fix staged-mode gating and merge-target correctness (Copilot review)
- Staged mode was a no-op: gh-aw does not inject GH_AW_SAFE_OUTPUTS_STAGED into custom safe-output jobs, and a step's own step-level env is not visible to that same step's `if:`. Set the flag explicitly in each custom job's env and gate destructive actions in-script (shell `$GH_AW_SAFE_OUTPUTS_STAGED` for run steps, `process.env` for github-script) instead of via step `if:`. - Merge target: merges now go through `gh pr merge` (targets each PR's base = default branch, not GITHUB_REF_NAME), so workflow_dispatch from a non-default branch can't merge into the wrong branch. Staged preview echoes github.event.repository.default_branch. - Reference secrets.OPENAPI_MERGE_TOKEN directly for GH_TOKEN (the previous `env.MERGE_TOKEN` indirection resolved empty for custom jobs). - Close-older-PRs now also requires success() so a failed merge won't close PRs. Recompiled the lock with gh aw. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcc1f083-b349-4398-ae7e-e2b90b1924b7
1 parent a8fef0b commit 4edc3aa

2 files changed

Lines changed: 57 additions & 41 deletions

File tree

.github/workflows/openapi-pr-reviewer.lock.yml

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9cc11dc458df1edec3a42d9cca931bb313fbd3f00f032a7022c2f746c1d12dbf","body_hash":"754e3baebfa788bc34791e9abcecf9ff11c1fbd66001673477acce8bb80db1a7","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
1+
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"2c28fd87cdda514ae405a76e3d502a7af58e4d1ed3ce4df44bb0ca834e343bc5","body_hash":"754e3baebfa788bc34791e9abcecf9ff11c1fbd66001673477acce8bb80db1a7","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
22
# gh-aw-manifest: {"version":1,"secrets":["CHATTERBOX_TOKEN","CHATTERBOX_URL","COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","OPENAPI_MERGE_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]}
33
# ___ _ _
44
# / _ \ | | (_)
@@ -1462,7 +1462,7 @@ jobs:
14621462
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
14631463
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
14641464
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1465-
MERGE_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
1465+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
14661466
with:
14671467
script: |
14681468
const fs = require('fs');
@@ -1475,9 +1475,15 @@ jobs:
14751475
core.setOutput('older_prs', item.older_prs || '');
14761476
core.setOutput('summary', item.summary || '');
14771477
- name: Merge the two latest PRs via the merge API
1478-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' }}
14791478
run: |
14801479
set -euo pipefail
1480+
# Staged/dry-run guard. We check the shell env var (injected by gh-aw) rather than a
1481+
# step `if:` because gh-aw sets GH_AW_SAFE_OUTPUTS_STAGED at step level, and a step's
1482+
# own step-level env is NOT visible to that same step's `if:` conditional.
1483+
if [ "${GH_AW_SAFE_OUTPUTS_STAGED:-}" = "true" ]; then
1484+
echo "🎭 Staged: would merge #${PR_30} + #${PR_31} into ${DEFAULT_BRANCH}."
1485+
exit 0
1486+
fi
14811487
# Merge each PR through the GitHub merge API (a merge commit, like the old --no-ff).
14821488
# These diffs are huge (~64 files / 250K lines), so the synchronous merge call can
14831489
# gateway-timeout (502/504) even though the merge completes server-side. Retry, and
@@ -1506,27 +1512,20 @@ jobs:
15061512
env:
15071513
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
15081514
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
1515+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
15091516
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1510-
GH_TOKEN: ${{ env.MERGE_TOKEN }}
1511-
MERGE_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
1512-
PR_30: ${{ steps.req.outputs.pr_30 }}
1513-
PR_31: ${{ steps.req.outputs.pr_31 }}
1514-
- name: Preview merge (staged)
1515-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED == 'true' }}
1516-
run: |
1517-
echo "🎭 Staged: would merge #${PR_30} + #${PR_31} into ${GITHUB_REF_NAME}, then close: ${OLDER:-none}"
1518-
env:
1519-
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
1520-
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
1521-
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1522-
MERGE_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
1523-
OLDER: ${{ steps.req.outputs.older_prs }}
1517+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
1518+
GH_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
15241519
PR_30: ${{ steps.req.outputs.pr_30 }}
15251520
PR_31: ${{ steps.req.outputs.pr_31 }}
15261521
- name: Close older superseded PRs
1527-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' && steps.req.outputs.older_prs != '' }}
1522+
if: ${{ success() && steps.req.outputs.older_prs != '' }}
15281523
run: |
15291524
set -euo pipefail
1525+
if [ "${GH_AW_SAFE_OUTPUTS_STAGED:-}" = "true" ]; then
1526+
echo "🎭 Staged: would close superseded PRs: ${OLDER:-none}"
1527+
exit 0
1528+
fi
15301529
IFS=',' read -ra NUMS <<< "$OLDER"
15311530
for n in "${NUMS[@]}"; do
15321531
n=$(echo "$n" | tr -d ' ')
@@ -1539,28 +1538,31 @@ jobs:
15391538
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
15401539
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
15411540
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1542-
GH_TOKEN: ${{ env.MERGE_TOKEN }}
1543-
MERGE_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
1541+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
1542+
GH_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
15441543
OLDER: ${{ steps.req.outputs.older_prs }}
15451544
PR_30: ${{ steps.req.outputs.pr_30 }}
15461545
PR_31: ${{ steps.req.outputs.pr_31 }}
15471546
- name: Notify chatterbox of merge
1548-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' }}
15491547
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
15501548
env:
15511549
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
15521550
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
15531551
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1554-
MERGE_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
1552+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
15551553
PR_30: ${{ steps.req.outputs.pr_30 }}
15561554
PR_31: ${{ steps.req.outputs.pr_31 }}
15571555
SUMMARY: ${{ steps.req.outputs.summary }}
15581556
with:
15591557
script: |
1558+
const text = `Merged OpenAPI description PRs #${process.env.PR_30} & #${process.env.PR_31} in <https://github.com/${process.env.GITHUB_REPOSITORY}|rest-api-description>. ${process.env.SUMMARY || ''}`.slice(0, 200);
1559+
if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === 'true') {
1560+
await core.summary.addRaw(`## 🎭 Staged: would post to chatterbox #api-platform\n\n${text}\n`).write();
1561+
return;
1562+
}
15601563
const url = process.env.CHATTERBOX_URL;
15611564
const token = process.env.CHATTERBOX_TOKEN;
15621565
if (!url || !token) { core.warning('chatterbox not configured; skipping notify'); return; }
1563-
const text = `Merged OpenAPI description PRs #${process.env.PR_30} & #${process.env.PR_31} in <https://github.com/${process.env.GITHUB_REPOSITORY}|rest-api-description>. ${process.env.SUMMARY || ''}`.slice(0, 200);
15641566
const endpoint = `${url.replace(/\/$/, '')}/topics/%23api-platform`;
15651567
const auth = Buffer.from(`${token}:`).toString('base64');
15661568
const res = await fetch(endpoint, {
@@ -1591,6 +1593,7 @@ jobs:
15911593
CHATTERBOX_TOKEN: ${{ secrets.CHATTERBOX_TOKEN }}
15921594
CHATTERBOX_URL: ${{ secrets.CHATTERBOX_URL }}
15931595
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
1596+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
15941597
with:
15951598
script: |
15961599
const fs = require('fs');

.github/workflows/openapi-pr-reviewer.md

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ safe-outputs:
6767
required: true
6868
type: string
6969
env:
70+
# gh-aw does NOT auto-inject GH_AW_SAFE_OUTPUTS_STAGED into custom safe-output jobs
71+
# (only into its own built-in jobs), so we set it explicitly here from the
72+
# `safe-outputs.staged` value. To go live, set this to "false" (or remove it) in BOTH
73+
# custom jobs AND set `safe-outputs.staged: false`, then recompile.
74+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
7075
CHATTERBOX_URL: "${{ secrets.CHATTERBOX_URL }}"
7176
CHATTERBOX_TOKEN: "${{ secrets.CHATTERBOX_TOKEN }}"
7277
steps:
@@ -136,10 +141,10 @@ safe-outputs:
136141
required: true
137142
type: string
138143
env:
139-
# Token used to merge the PRs and close the older ones. Merging a PR is the compliant
140-
# path through the "require a pull request" branch rule, so this only needs
141-
# `pull-requests: write` + `contents: write` — no protected-branch push/bypass.
142-
MERGE_TOKEN: "${{ secrets.OPENAPI_MERGE_TOKEN }}"
144+
# See note on the post-to-chatterbox job: gh-aw doesn't inject this into custom
145+
# safe-output jobs, so we set it explicitly. Flip to "false" here + in
146+
# `safe-outputs.staged` to go live, then recompile.
147+
GH_AW_SAFE_OUTPUTS_STAGED: "true"
143148
CHATTERBOX_URL: "${{ secrets.CHATTERBOX_URL }}"
144149
CHATTERBOX_TOKEN: "${{ secrets.CHATTERBOX_TOKEN }}"
145150
steps:
@@ -158,13 +163,22 @@ safe-outputs:
158163
core.setOutput('older_prs', item.older_prs || '');
159164
core.setOutput('summary', item.summary || '');
160165
- name: Merge the two latest PRs via the merge API
161-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' }}
162166
env:
163-
GH_TOKEN: ${{ env.MERGE_TOKEN }}
167+
# Use the secret directly: gh-aw injects the job `env:` at step level, and a
168+
# step-level env value can't reference another same-step env var via `env.`.
169+
GH_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
164170
PR_30: ${{ steps.req.outputs.pr_30 }}
165171
PR_31: ${{ steps.req.outputs.pr_31 }}
172+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
166173
run: |
167174
set -euo pipefail
175+
# Staged/dry-run guard. We check the shell env var (injected by gh-aw) rather than a
176+
# step `if:` because gh-aw sets GH_AW_SAFE_OUTPUTS_STAGED at step level, and a step's
177+
# own step-level env is NOT visible to that same step's `if:` conditional.
178+
if [ "${GH_AW_SAFE_OUTPUTS_STAGED:-}" = "true" ]; then
179+
echo "🎭 Staged: would merge #${PR_30} + #${PR_31} into ${DEFAULT_BRANCH}."
180+
exit 0
181+
fi
168182
# Merge each PR through the GitHub merge API (a merge commit, like the old --no-ff).
169183
# These diffs are huge (~64 files / 250K lines), so the synchronous merge call can
170184
# gateway-timeout (502/504) even though the merge completes server-side. Retry, and
@@ -190,23 +204,19 @@ safe-outputs:
190204
# Merge 3.0 first, then 3.1 (they touch disjoint paths, so no conflicts).
191205
merge_pr "$PR_30" "OpenAPI 3.0"
192206
merge_pr "$PR_31" "OpenAPI 3.1"
193-
- name: Preview merge (staged)
194-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED == 'true' }}
195-
env:
196-
PR_30: ${{ steps.req.outputs.pr_30 }}
197-
PR_31: ${{ steps.req.outputs.pr_31 }}
198-
OLDER: ${{ steps.req.outputs.older_prs }}
199-
run: |
200-
echo "🎭 Staged: would merge #${PR_30} + #${PR_31} into ${GITHUB_REF_NAME}, then close: ${OLDER:-none}"
201207
- name: Close older superseded PRs
202-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' && steps.req.outputs.older_prs != '' }}
208+
if: ${{ success() && steps.req.outputs.older_prs != '' }}
203209
env:
204-
GH_TOKEN: ${{ env.MERGE_TOKEN }}
210+
GH_TOKEN: ${{ secrets.OPENAPI_MERGE_TOKEN }}
205211
PR_30: ${{ steps.req.outputs.pr_30 }}
206212
PR_31: ${{ steps.req.outputs.pr_31 }}
207213
OLDER: ${{ steps.req.outputs.older_prs }}
208214
run: |
209215
set -euo pipefail
216+
if [ "${GH_AW_SAFE_OUTPUTS_STAGED:-}" = "true" ]; then
217+
echo "🎭 Staged: would close superseded PRs: ${OLDER:-none}"
218+
exit 0
219+
fi
210220
IFS=',' read -ra NUMS <<< "$OLDER"
211221
for n in "${NUMS[@]}"; do
212222
n=$(echo "$n" | tr -d ' ')
@@ -216,18 +226,21 @@ safe-outputs:
216226
echo "warning: failed to close #$n"
217227
done
218228
- name: Notify chatterbox of merge
219-
if: ${{ env.GH_AW_SAFE_OUTPUTS_STAGED != 'true' }}
220229
uses: actions/github-script@v9
221230
env:
222231
PR_30: ${{ steps.req.outputs.pr_30 }}
223232
PR_31: ${{ steps.req.outputs.pr_31 }}
224233
SUMMARY: ${{ steps.req.outputs.summary }}
225234
with:
226235
script: |
236+
const text = `Merged OpenAPI description PRs #${process.env.PR_30} & #${process.env.PR_31} in <https://github.com/${process.env.GITHUB_REPOSITORY}|rest-api-description>. ${process.env.SUMMARY || ''}`.slice(0, 200);
237+
if (process.env.GH_AW_SAFE_OUTPUTS_STAGED === 'true') {
238+
await core.summary.addRaw(`## 🎭 Staged: would post to chatterbox #api-platform\n\n${text}\n`).write();
239+
return;
240+
}
227241
const url = process.env.CHATTERBOX_URL;
228242
const token = process.env.CHATTERBOX_TOKEN;
229243
if (!url || !token) { core.warning('chatterbox not configured; skipping notify'); return; }
230-
const text = `Merged OpenAPI description PRs #${process.env.PR_30} & #${process.env.PR_31} in <https://github.com/${process.env.GITHUB_REPOSITORY}|rest-api-description>. ${process.env.SUMMARY || ''}`.slice(0, 200);
231244
const endpoint = `${url.replace(/\/$/, '')}/topics/%23api-platform`;
232245
const auth = Buffer.from(`${token}:`).toString('base64');
233246
const res = await fetch(endpoint, {

0 commit comments

Comments
 (0)