From 5181b6c1e25f856966c0335966fa7a71e8e29ae9 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 14:53:37 -0800 Subject: [PATCH 01/12] Fix auth issues in issue management workflow --- .github/workflows/stale-issue-cleanup.yml | 59 ++++++++++++++++++----- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 96b8a8b..a59e1b7 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -1,17 +1,48 @@ name: Stale Issue Cleanup -# Authentication: -# This workflow requires a token with the following scopes: -# - issues:write (to comment on and close issues) -# - read:org (to read team membership) -# - read:project and project (to update the Resolution project field) -# For experimentation, use a PAT stored as STALE_ISSUES_TOKEN repo secret. -# For production, replace with a GitHub App token to avoid manual renewal: -# - Register one "Storage Explorer Automation" GitHub App in the microsoft org -# - Grant it issues:write, members:read, and projects:write permissions -# - Install it on this repo (and any other repos your agents need) -# - Use actions/create-github-app-token to generate tokens at runtime -# - One app can serve all agent workflows +# # Authentication +# +# This workflow uses two tokens for two different purposes: +# +# 1. `github.token` (the built-in Actions token, controlled by the `permissions` block below) +# - `models: read` — lets `agency copilot` call the GitHub Copilot/Models API for AI inference +# - `issues: write` — lets the agent comment on and close issues +# - `contents: read` — lets the agent read repository files (e.g., agent/skill definitions) +# +# Passed as `COPILOT_GITHUB_TOKEN` so that `agency copilot` uses it specifically for +# Copilot model auth. The tool checks env vars in this priority order: +# `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` +# +# By setting `COPILOT_GITHUB_TOKEN`, we ensure `github.token` (which has `models:read`) +# is used for Copilot inference, and `GH_TOKEN` (`STALE_ISSUES_TOKEN`) is reserved for the +# gh CLI's GitHub API calls. If `COPILOT_GITHUB_TOKEN` were not set, the tool would try +# `GH_TOKEN` first — and since `STALE_ISSUES_TOKEN` doesn't have Copilot API access, auth +# would fail even though a valid token for Copilot is available. +# +# 2. `STALE_ISSUES_TOKEN` secret (a PAT or GitHub App token stored as a repo secret) +# - `read:org` — required to list members of the microsoft/azure-storage-explorer +# GitHub team. This is an organization-scoped permission that the `permissions` block +# cannot grant to `github.token` — you cannot add it to the `permissions` block no matter +# what you put there. +# - `read:project` — required to look up project item IDs on the org-level project +# - `project` — required to update the Resolution field on the org-level project +# +# These are org-level permissions that must come from an external token (PAT or App). +# +# For experimentation, use a PAT stored as `STALE_ISSUES_TOKEN` repo secret. +# For production, replace with a GitHub App token to avoid manual renewal: +# - Register one "Storage Explorer Automation" GitHub App in the microsoft org +# - Grant it `members:read` and `projects:write` permissions +# - Install it on this repo (and any other repos your agents need) +# - Use `actions/create-github-app-token` to generate tokens at runtime +# - One app can serve all agent workflows +# +# # Install Step Note +# +# The "Install Agency" step downloads from aka.ms/InstallTool.sh. GitHub's hosted runners +# have unrestricted outbound internet access to public URLs, so this URL is reachable. +# The script downloads the `agency` binary from its release host; as long as that host is +# a public URL (GitHub releases, CDN, etc.) it will also be reachable. on: schedule: @@ -21,6 +52,7 @@ on: permissions: issues: write contents: read + models: read jobs: cleanup: @@ -31,11 +63,12 @@ jobs: - name: Install Agency run: | - curl -sSfL https://aka.ms/InstallTool.sh | sh -s agency + curl -fsSL https://aka.ms/InstallTool.sh | sh -s agency echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Run stale issue cleanup agent env: + COPILOT_GITHUB_TOKEN: ${{ github.token }} GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | agency copilot \ From 3a311e2ffd958ac9b94ddfd7298bcc2076b01335 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:12:22 +0000 Subject: [PATCH 02/12] Use Copilot CLI instead of Agency --- .github/workflows/stale-issue-cleanup.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index a59e1b7..676c2a6 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -39,10 +39,8 @@ name: Stale Issue Cleanup # # # Install Step Note # -# The "Install Agency" step downloads from aka.ms/InstallTool.sh. GitHub's hosted runners -# have unrestricted outbound internet access to public URLs, so this URL is reachable. -# The script downloads the `agency` binary from its release host; as long as that host is -# a public URL (GitHub releases, CDN, etc.) it will also be reachable. +# The "Install Copilot CLI" step downloads from gh.io/copilot-install, which is a public +# GitHub URL that completes in ~3 seconds on GitHub-hosted runners. on: schedule: @@ -61,9 +59,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Agency + - name: Install Copilot CLI run: | - curl -fsSL https://aka.ms/InstallTool.sh | sh -s agency + curl -fsSL https://gh.io/copilot-install | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Run stale issue cleanup agent @@ -71,7 +69,7 @@ jobs: COPILOT_GITHUB_TOKEN: ${{ github.token }} GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | - agency copilot \ + copilot \ --agent issue-manager \ --prompt "Find and close stale bug issues." \ --allow-all-tools \ From 3d5a6871de9cf10e3aaf6ee8d17d8d4aadfbd822 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:40:31 +0000 Subject: [PATCH 03/12] More token fixes --- .github/workflows/stale-issue-cleanup.yml | 47 ++++++++--------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 676c2a6..97061b5 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -2,40 +2,27 @@ name: Stale Issue Cleanup # # Authentication # -# This workflow uses two tokens for two different purposes: +# This workflow uses a single token, `STALE_ISSUES_TOKEN`, for all operations. # -# 1. `github.token` (the built-in Actions token, controlled by the `permissions` block below) -# - `models: read` — lets `agency copilot` call the GitHub Copilot/Models API for AI inference -# - `issues: write` — lets the agent comment on and close issues -# - `contents: read` — lets the agent read repository files (e.g., agent/skill definitions) +# `STALE_ISSUES_TOKEN` is a Classic PAT with the following scopes: # -# Passed as `COPILOT_GITHUB_TOKEN` so that `agency copilot` uses it specifically for -# Copilot model auth. The tool checks env vars in this priority order: -# `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` +# - `repo` — lets the gh CLI comment on and close issues +# - `read:org` — required to list members of the microsoft/azure-storage-explorer team +# - `copilot` — required by the Copilot CLI for model inference (this is why github.token +# cannot be used: it is a machine installation token, not a user PAT, and +#. the Copilot CLI rejects it regardless of any `permissions` block settings) +# - `project` — required to update the Resolution field on the org-level project # -# By setting `COPILOT_GITHUB_TOKEN`, we ensure `github.token` (which has `models:read`) -# is used for Copilot inference, and `GH_TOKEN` (`STALE_ISSUES_TOKEN`) is reserved for the -# gh CLI's GitHub API calls. If `COPILOT_GITHUB_TOKEN` were not set, the tool would try -# `GH_TOKEN` first — and since `STALE_ISSUES_TOKEN` doesn't have Copilot API access, auth -# would fail even though a valid token for Copilot is available. +# `read:org` and `project` are org-level scopes that cannot be granted to github.token via +# the workflow `permissions` block, which is why an external PAT is always required here. # -# 2. `STALE_ISSUES_TOKEN` secret (a PAT or GitHub App token stored as a repo secret) -# - `read:org` — required to list members of the microsoft/azure-storage-explorer -# GitHub team. This is an organization-scoped permission that the `permissions` block -# cannot grant to `github.token` — you cannot add it to the `permissions` block no matter -# what you put there. -# - `read:project` — required to look up project item IDs on the org-level project -# - `project` — required to update the Resolution field on the org-level project +# Because `GH_TOKEN` is set to this PAT, both the Copilot CLI (for model inference) and the +# gh CLI (for GitHub API calls) pick it up automatically. # -# These are org-level permissions that must come from an external token (PAT or App). -# -# For experimentation, use a PAT stored as `STALE_ISSUES_TOKEN` repo secret. -# For production, replace with a GitHub App token to avoid manual renewal: -# - Register one "Storage Explorer Automation" GitHub App in the microsoft org -# - Grant it `members:read` and `projects:write` permissions -# - Install it on this repo (and any other repos your agents need) -# - Use `actions/create-github-app-token` to generate tokens at runtime -# - One app can serve all agent workflows +# For production, consider replacing this PAT with a GitHub App token for the gh CLI calls +# (org membership, issue operations, project updates), using `actions/create-github-app-token`. +# Note that a GitHub App cannot hold a Copilot seat, so Copilot inference will always require +# a user PAT with the `copilot` scope. # # # Install Step Note # @@ -48,7 +35,6 @@ on: workflow_dispatch: # Allow manual triggers permissions: - issues: write contents: read models: read @@ -66,7 +52,6 @@ jobs: - name: Run stale issue cleanup agent env: - COPILOT_GITHUB_TOKEN: ${{ github.token }} GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | copilot \ From d10560e8b96f9ce27c9a1791f4794a19f9896a36 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 15:48:22 -0800 Subject: [PATCH 04/12] Remove unneeded models: read permission --- .github/workflows/stale-issue-cleanup.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 97061b5..3eda540 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -36,7 +36,6 @@ on: permissions: contents: read - models: read jobs: cleanup: From 61caf9cf300c0e7daf0d3b6c1113558534d6688b Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 16:06:59 -0800 Subject: [PATCH 05/12] Add temporary diagnostic step --- .github/workflows/stale-issue-cleanup.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 3eda540..1888e00 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -49,6 +49,20 @@ jobs: curl -fsSL https://gh.io/copilot-install | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - name: Verify token (diagnostic - remove after first successful run) + env: + GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} + run: | + HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $GH_TOKEN" \ + https://api.github.com/user) + echo "GitHub API /user HTTP status: $HTTP_STATUS" + if [ "$HTTP_STATUS" != "200" ]; then + echo "::error::STALE_ISSUES_TOKEN is invalid (HTTP $HTTP_STATUS). Please update the repo secret with a fresh PAT." + exit 1 + fi + echo "Token is valid." + - name: Run stale issue cleanup agent env: GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} From b2baef28e2a1f0b01257f8bae663c28df9062fbe Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 16:10:47 -0800 Subject: [PATCH 06/12] Try different env var --- .github/workflows/stale-issue-cleanup.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 1888e00..0ee66f7 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -49,22 +49,9 @@ jobs: curl -fsSL https://gh.io/copilot-install | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Verify token (diagnostic - remove after first successful run) - env: - GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} - run: | - HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: token $GH_TOKEN" \ - https://api.github.com/user) - echo "GitHub API /user HTTP status: $HTTP_STATUS" - if [ "$HTTP_STATUS" != "200" ]; then - echo "::error::STALE_ISSUES_TOKEN is invalid (HTTP $HTTP_STATUS). Please update the repo secret with a fresh PAT." - exit 1 - fi - echo "Token is valid." - - name: Run stale issue cleanup agent env: + COPILOT_GITHUB_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | copilot \ From 70fb78b1c6a904ceb59e07f2e7c604cab9842cdc Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 16:16:26 -0800 Subject: [PATCH 07/12] Add different diagnostic --- .github/workflows/stale-issue-cleanup.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 0ee66f7..fe0ece0 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -49,6 +49,16 @@ jobs: curl -fsSL https://gh.io/copilot-install | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - name: Diagnostics + env: + GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} + run: | + echo "Token prefix: ${GH_TOKEN:0:12}" + echo "Token length: ${#GH_TOKEN}" + copilot --version + echo '--- gh auth status ---' + gh auth status + - name: Run stale issue cleanup agent env: COPILOT_GITHUB_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} From 70e8a715ce9ef364ec7707c9500e3cda25793b09 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 16:52:53 -0800 Subject: [PATCH 08/12] Try indirect approach --- .github/workflows/stale-issue-cleanup.yml | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index fe0ece0..2464f5c 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -49,21 +49,11 @@ jobs: curl -fsSL https://gh.io/copilot-install | bash echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - name: Diagnostics - env: - GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} - run: | - echo "Token prefix: ${GH_TOKEN:0:12}" - echo "Token length: ${#GH_TOKEN}" - copilot --version - echo '--- gh auth status ---' - gh auth status - - name: Run stale issue cleanup agent env: - COPILOT_GITHUB_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | + echo "$GH_TOKEN" | gh auth login --with-token copilot \ --agent issue-manager \ --prompt "Find and close stale bug issues." \ From ea5601a57af6308abef817b49cb674bfd5eb2284 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 17:03:31 -0800 Subject: [PATCH 09/12] More indirect fixes --- .github/workflows/stale-issue-cleanup.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 2464f5c..6e923c1 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -51,9 +51,10 @@ jobs: - name: Run stale issue cleanup agent env: - GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} + STALE_ISSUES_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | - echo "$GH_TOKEN" | gh auth login --with-token + echo "$STALE_ISSUES_TOKEN" | gh auth login --with-token + export GH_TOKEN="$STALE_ISSUES_TOKEN" copilot \ --agent issue-manager \ --prompt "Find and close stale bug issues." \ From 0199683a0ad7dd25877aa62d7f4554be5ede1769 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 17:23:48 -0800 Subject: [PATCH 10/12] Use multiple secrets --- .github/workflows/stale-issue-cleanup.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 6e923c1..263a3a4 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -51,10 +51,9 @@ jobs: - name: Run stale issue cleanup agent env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_TOKEN }} STALE_ISSUES_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | - echo "$STALE_ISSUES_TOKEN" | gh auth login --with-token - export GH_TOKEN="$STALE_ISSUES_TOKEN" copilot \ --agent issue-manager \ --prompt "Find and close stale bug issues." \ From 3665f54a288de9b9aec6fdabe1e06ed0c21cbf36 Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Fri, 20 Feb 2026 17:51:21 -0800 Subject: [PATCH 11/12] Update comment --- .github/workflows/stale-issue-cleanup.yml | 47 +++++++++++++++-------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 263a3a4..253e95f 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -2,27 +2,42 @@ name: Stale Issue Cleanup # # Authentication # -# This workflow uses a single token, `STALE_ISSUES_TOKEN`, for all operations. +# This workflow uses two secrets: # -# `STALE_ISSUES_TOKEN` is a Classic PAT with the following scopes: +# 1. `COPILOT_TOKEN` — a Fine-Grained Personal Access Token for Copilot model inference # -# - `repo` — lets the gh CLI comment on and close issues -# - `read:org` — required to list members of the microsoft/azure-storage-explorer team -# - `copilot` — required by the Copilot CLI for model inference (this is why github.token -# cannot be used: it is a machine installation token, not a user PAT, and -#. the Copilot CLI rejects it regardless of any `permissions` block settings) -# - `project` — required to update the Resolution field on the org-level project +# The Copilot CLI (v0.0.413+) accepts only OAuth tokens or Fine-Grained PATs +# (prefix: github_pat_). Classic PATs (prefix: ghp_) are rejected with +# "No authentication information found" regardless of which env var they are placed in +# or whether they have the `copilot` scope. # -# `read:org` and `project` are org-level scopes that cannot be granted to github.token via -# the workflow `permissions` block, which is why an external PAT is always required here. +# To create this token: # -# Because `GH_TOKEN` is set to this PAT, both the Copilot CLI (for model inference) and the -# gh CLI (for GitHub API calls) pick it up automatically. +# - Go to github.com/settings/personal-access-tokens/new +# - Resource owner: your personal account +# - Repository access: None (no repository permissions needed) +# - Expiration: set as appropriate +# - Account permissions: Copilot Requests -> Read-only +# - Store as `COPILOT_TOKEN` repo secret # -# For production, consider replacing this PAT with a GitHub App token for the gh CLI calls -# (org membership, issue operations, project updates), using `actions/create-github-app-token`. -# Note that a GitHub App cannot hold a Copilot seat, so Copilot inference will always require -# a user PAT with the `copilot` scope. +# 2. `STALE_ISSUES_TOKEN` — a Classic PAT for GitHub CLI API calls +# +# Fine-Grained PATs in the microsoft org require org owner approval, so a Classic PAT +# is used for org-level operations. Required scopes: +# +# - `read:org` — list members of the microsoft/azure-storage-explorer team +# - `project` — update the Resolution field on the org-level project +# - `repo` — comment on and close issues +# +# PATs are a temporary onboarding mechanism. For production, consider replacing with a +# GitHub App token to avoid manual renewal: +# +# - Register a "Storage Explorer Automation" GitHub App in the microsoft org +# - Grant it `members:read` and `projects:write` permissions +# - Install it on this repo +# - Use `actions/create-github-app-token` to generate tokens at runtime +# Note: a GitHub App cannot hold a Copilot seat, so COPILOT_TOKEN will always require +# a personal Fine-Grained PAT. # # # Install Step Note # From c9ba79f0e1cbbf20d3e639cd0e0631e2294e6ddc Mon Sep 17 00:00:00 2001 From: Craig Alvord Date: Mon, 23 Feb 2026 09:55:14 -0800 Subject: [PATCH 12/12] Use correct token name --- .github/workflows/stale-issue-cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale-issue-cleanup.yml b/.github/workflows/stale-issue-cleanup.yml index 253e95f..e5a580e 100644 --- a/.github/workflows/stale-issue-cleanup.yml +++ b/.github/workflows/stale-issue-cleanup.yml @@ -67,7 +67,7 @@ jobs: - name: Run stale issue cleanup agent env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_TOKEN }} - STALE_ISSUES_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} + GH_TOKEN: ${{ secrets.STALE_ISSUES_TOKEN }} run: | copilot \ --agent issue-manager \