Copilot commented on code in PR #13525:
URL: https://github.com/apache/cloudstack/pull/13525#discussion_r3511085521
##########
.github/workflows/daily-issue-triage.md:
##########
@@ -14,6 +14,77 @@ permissions: read-all
network: defaults
+# Rotates the Copilot token across volunteer PATs, see
.github/COPILOT_TOKENS.md.
+# Strict mode forbids reading secrets in the agent job, so this job picks
today's
+# token and outputs its alias only; the agent job resolves the secret itself.
+# ROTATION_SLOT 1 staggers this workflow half the pool away from
+# daily-repo-status so the two pick different tokens (pool of 2 or more).
+# After `gh aw compile`, run `bash .github/scripts/post-compile.sh` to re-wire
the
+# agent job to this output.
+jobs:
+ pick_copilot_token:
+ runs-on: ubuntu-latest
+ outputs:
+ name: ${{ steps.pick.outputs.name }}
+ steps:
+ - name: Compute candidate names by date
+ id: names
+ env:
+ NAMES_JSON: "${{ vars.GH_AW_COPILOT_TOKEN_NAMES }}"
+ ROTATION_SLOT: "1"
+ run: |
+ set -euo pipefail
+ NAMES=()
+ if [ -n "${NAMES_JSON:-}" ]; then
+ mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]')
+ fi
+ N=${#NAMES[@]}
+ K=3 # today's pick plus 2 fallbacks in case it's dead
+ if [ "$N" -eq 0 ]; then
+ for o in $(seq 0 $((K-1))); do echo "name_$o=" >>
"$GITHUB_OUTPUT"; done
+ echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base
COPILOT_GITHUB_TOKEN"
+ exit 0
+ fi
+ DOY=$(date -u +%-j)
+ # slot 1 starts half the pool away from slot 0 so the two workflows
+ # pick different tokens whenever the pool has at least 2
+ START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N ))
+ for o in $(seq 0 $((K-1))); do
+ i=$(( (START + o) % N ))
+ echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT"
+ done
+ - name: Pick first live token name
+ id: pick
+ env:
+ NAME_0: "${{ steps.names.outputs.name_0 }}"
+ NAME_1: "${{ steps.names.outputs.name_1 }}"
+ NAME_2: "${{ steps.names.outputs.name_2 }}"
+ CAND_0: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_0)] }}"
+ CAND_1: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_1)] }}"
+ CAND_2: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_2)] }}"
+ BASE: "${{ secrets.COPILOT_GITHUB_TOKEN }}"
+ run: |
+ set -euo pipefail
+ live() {
+ [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \
+ -H "Authorization: Bearer $1" https://api.github.com/user ||
echo 000)" = "200" ]
+ }
Review Comment:
The token liveness check is hardcoded to https://api.github.com/user. Using
the built-in GITHUB_API_URL makes this work on GitHub Enterprise Server as well
as github.com.
##########
.github/workflows/daily-repo-status.md:
##########
@@ -20,6 +20,75 @@ engine:
id: copilot
model: claude-haiku-4.5
+# Rotates the Copilot token across volunteer PATs, see
.github/COPILOT_TOKENS.md.
+# Strict mode forbids reading secrets in the agent job, so this job picks
today's
+# token and outputs its alias only; the agent job resolves the secret itself.
+# After `gh aw compile`, run `bash .github/scripts/post-compile.sh` to re-wire
the
+# agent job to this output.
+jobs:
+ pick_copilot_token:
+ runs-on: ubuntu-latest
+ outputs:
+ name: ${{ steps.pick.outputs.name }}
+ steps:
+ - name: Compute candidate names by date
+ id: names
+ env:
+ NAMES_JSON: "${{ vars.GH_AW_COPILOT_TOKEN_NAMES }}"
+ ROTATION_SLOT: "0"
+ run: |
+ set -euo pipefail
+ NAMES=()
+ if [ -n "${NAMES_JSON:-}" ]; then
+ mapfile -t NAMES < <(printf '%s' "$NAMES_JSON" | jq -r '.[]')
+ fi
+ N=${#NAMES[@]}
+ K=3 # today's pick plus 2 fallbacks in case it's dead
+ if [ "$N" -eq 0 ]; then
+ for o in $(seq 0 $((K-1))); do echo "name_$o=" >>
"$GITHUB_OUTPUT"; done
+ echo "GH_AW_COPILOT_TOKEN_NAMES is empty -> agent will use base
COPILOT_GITHUB_TOKEN"
+ exit 0
+ fi
+ DOY=$(date -u +%-j)
+ # slot 1 starts half the pool away from slot 0 so the two workflows
+ # pick different tokens whenever the pool has at least 2
+ START=$(( (DOY - 1 + ROTATION_SLOT * ((N + 1) / 2)) % N ))
+ for o in $(seq 0 $((K-1))); do
+ i=$(( (START + o) % N ))
+ echo "name_$o=${NAMES[$i]}" >> "$GITHUB_OUTPUT"
+ done
+ - name: Pick first live token name
+ id: pick
+ env:
+ NAME_0: "${{ steps.names.outputs.name_0 }}"
+ NAME_1: "${{ steps.names.outputs.name_1 }}"
+ NAME_2: "${{ steps.names.outputs.name_2 }}"
+ CAND_0: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_0)] }}"
+ CAND_1: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_1)] }}"
+ CAND_2: "${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}',
steps.names.outputs.name_2)] }}"
+ BASE: "${{ secrets.COPILOT_GITHUB_TOKEN }}"
+ run: |
+ set -euo pipefail
+ live() {
+ [ -n "$1" ] && [ "$(curl -s -o /dev/null -w '%{http_code}' \
+ -H "Authorization: Bearer $1" https://api.github.com/user ||
echo 000)" = "200" ]
+ }
Review Comment:
The token liveness check is hardcoded to https://api.github.com/user, which
breaks on GitHub Enterprise Server (and is inconsistent with the workflow
context vars). Prefer using the runner-provided GITHUB_API_URL so the same
logic works on GHES and github.com.
##########
.github/workflows/copilot-token-health.yml:
##########
@@ -0,0 +1,64 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Manual health check for the pool of volunteer Copilot tokens (see
.github/COPILOT_TOKENS.md).
+# Trigger it from the Actions tab to find dead tokens in
GH_AW_COPILOT_TOKEN_NAMES so they can be
+# pruned. Only HTTP status codes are printed, never account logins. A 200 just
means the token is
+# live; there is no endpoint to check whether its monthly Copilot requests are
used up.
+name: Copilot token health
+
+on:
+ workflow_dispatch: {}
+
+permissions: {}
+
+jobs:
+ resolve:
+ runs-on: ubuntu-latest
+ outputs:
+ names: ${{ steps.list.outputs.names }}
+ steps:
+ - id: list
+ env:
+ NAMES: ${{ vars.GH_AW_COPILOT_TOKEN_NAMES || '[]' }}
+ run: echo "names=$NAMES" >> "$GITHUB_OUTPUT"
+
+ check:
+ needs: resolve
+ if: ${{ needs.resolve.outputs.names != '[]' && needs.resolve.outputs.names
!= '' }}
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ name: ${{ fromJson(needs.resolve.outputs.names) }}
+ steps:
+ - name: Check token liveness
+ env:
+ TOKEN: ${{ secrets[format('COPILOT_GITHUB_TOKEN_{0}', matrix.name)]
}}
+ run: |
+ set -euo pipefail
+ if [ -z "${TOKEN:-}" ]; then
+ echo "::error::no secret COPILOT_GITHUB_TOKEN_${{ matrix.name }}
found for registered alias '${{ matrix.name }}'"
+ exit 1
+ fi
+ code=$(curl -s -o /dev/null -w '%{http_code}' \
+ -H "Authorization: Bearer $TOKEN" https://api.github.com/user ||
echo 000)
+ echo "token '${{ matrix.name }}': HTTP $code"
Review Comment:
This health check calls https://api.github.com/user directly. Using
GITHUB_API_URL keeps it compatible with GitHub Enterprise Server without
changing behavior on github.com.
##########
.github/workflows/daily-repo-status.lock.yml:
##########
@@ -136,7 +136,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #
v6.0.3
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
Review Comment:
This lock file pins actions/checkout to v6.0.2 (de0fac2e…), but the rest of
the repo’s workflows consistently pin actions/checkout v6.0.3 (df4cb1c…; e.g.
.github/workflows/ci.yml:32). If this downgrade is accidental, consider
re-compiling/updating the lock file to match the repo-wide pin for consistency
and to avoid missing patch-level fixes.
##########
.github/workflows/daily-issue-triage.lock.yml:
##########
@@ -135,7 +135,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Checkout .github and .agents folders
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #
v6.0.3
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #
v6.0.2
Review Comment:
This lock file pins actions/checkout to v6.0.2 (de0fac2e…), while other
workflows in the repo consistently pin actions/checkout v6.0.3 (df4cb1c…; e.g.
.github/workflows/ci.yml:32). If unintentional, consider re-compiling/updating
to the repo-wide pin so workflow dependencies stay aligned.
##########
.github/scripts/post-compile.sh:
##########
@@ -0,0 +1,148 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Re-applies the token round-robin wiring to the gh-aw generated .lock.yml
files,
+# which `gh aw compile` doesn't know about. Run after every compile:
+#
+# gh aw compile && bash .github/scripts/post-compile.sh
+#
+# Three edits per lock file (see .github/COPILOT_TOKENS.md for the design):
+# - point the agent execute step's COPILOT_GITHUB_TOKEN at the
pick_copilot_token
+# job's output, falling back to the base secret
+# - point the agent job's "Redact secrets in logs" step at the same rotated
token,
+# so a volunteer token is scrubbed from uploaded artifacts, not just the
base one
+# - make the agent job depend on pick_copilot_token, and strip the
self-reference
+# gh-aw sometimes adds to pick_copilot_token's own needs (that would be a
cycle)
+#
+# Safe to re-run; a second run is a no-op.
+
+set -euo pipefail
+
+cd "$(git rev-parse --show-toplevel)"
+
+# Kept in an env var so perl doesn't try to interpolate the ${{ }} bits.
+export NEWVAL='${{ needs.pick_copilot_token.outputs.name != '"'"''"'"' &&
secrets[format('"'"'COPILOT_GITHUB_TOKEN_{0}'"'"',
needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN }}'
+
+FILES=(
+ ".github/workflows/daily-repo-status.lock.yml"
+ ".github/workflows/daily-issue-triage.lock.yml"
+)
+
+fail() { echo "ERROR: $1" >&2; exit 1; }
+
+# Fixes up `needs:` for the agent and pick_copilot_token jobs only; gh-aw adds
+# pick_copilot_token to several other jobs' needs and those must stay as-is.
+# Reads stdin, writes stdout. gh-aw emits inline needs (needs: foo) for single
+# dependencies and block form for lists; inline is fine unless it needs
editing,
+# in which case "INLINE_NEEDS:<job>" is printed to stderr and the caller bails.
+normalise_needs() {
+ awk '
+ function isjob(l){ return (l ~ /^ [A-Za-z0-9_-]+:[ \t]*$/) }
+ BEGIN { job=""; inneeds=0; agentpick=0 }
+ {
+ line=$0
+ if (isjob(line)) {
+ if (inneeds && job=="agent" && !agentpick) print " -
pick_copilot_token"
+ inneeds=0; agentpick=0
+ name=line; sub(/^ /,"",name); sub(/:[ \t]*$/,"",name); job=name
+ print line; next
+ }
+ if (line ~ /^ needs:[ \t]*[^ \t]/) {
+ if (job=="agent" && line !~ /pick_copilot_token/) print
"INLINE_NEEDS:" job > "/dev/stderr"
+ if (job=="pick_copilot_token" && line ~ /pick_copilot_token/) print
"INLINE_NEEDS:" job > "/dev/stderr"
+ print line; next
+ }
+ if (line ~ /^ needs:[ \t]*$/) { inneeds=1; agentpick=0; print line;
next }
+ if (inneeds) {
+ if (line ~ /^ - /) {
+ item=line; sub(/^ - /,"",item); gsub(/[ \t\r]/,"",item)
+ if (job=="pick_copilot_token" && item=="pick_copilot_token") next
+ if (job=="agent" && item=="pick_copilot_token") agentpick=1
+ print line; next
+ } else {
+ if (job=="agent" && !agentpick) print " - pick_copilot_token"
+ inneeds=0
+ print line; next
+ }
+ }
+ print line
+ }
+ END { if (inneeds && job=="agent" && !agentpick) print " -
pick_copilot_token" }
+ '
+}
+
+for f in "${FILES[@]}"; do
+ if [ ! -f "$f" ]; then
+ echo "WARN: $f not found, run 'gh aw compile' first? Skipping" >&2
+ continue
+ fi
+
+ # Repoint the agent execute step's token. The anchor is the GH_AW_PHASE:
agent env
+ # var further down the same env block; the detection job's block has
+ # GH_AW_PHASE: detection so it doesn't match and keeps the base token.
+ before=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN
}}' "$f" || true)
+ perl -0pi -e \
+ 's/^([ \t]*)COPILOT_GITHUB_TOKEN:[ \t]*\$\{\{[
\t]*secrets\.COPILOT_GITHUB_TOKEN[ \t]*\}\}[ \t]*\n(?=(?:[
\t]+[A-Z][A-Za-z0-9_]*:[^\n]*\n)*?[ \t]+GH_AW_PHASE:[ \t]*agent[
\t]*\n)/$1."COPILOT_GITHUB_TOKEN: ".$ENV{NEWVAL}."\n"/me' \
+ "$f"
+ after=$(grep -cF 'COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}'
"$f" || true)
+ removed=$(( before - after ))
+ if [ "$removed" -eq 1 ]; then token_edit="applied"
+ elif [ "$removed" -eq 0 ] && grep -qF
'needs.pick_copilot_token.outputs.name)] || secrets.COPILOT_GITHUB_TOKEN' "$f";
then token_edit="already"
+ else fail "$f: execute-step token line not patched as expected
(removed=$removed), anchor drifted?"
+ fi
Review Comment:
The "already patched" detection for the agent COPILOT_GITHUB_TOKEN edit is
too broad: it greps for a substring that is also present in the redact-step
patch. That can incorrectly treat a partially-patched lock file as fully
patched, leaving the agent step still using the base token.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]