Skip to content

fix: restore copilot-requests scope and apply least-privilege permissions - #78

Merged
austenstone merged 2 commits into
mainfrom
austenstone-workflow-hardening
Aug 20, 2026
Merged

fix: restore copilot-requests scope and apply least-privilege permissions#78
austenstone merged 2 commits into
mainfrom
austenstone-workflow-hardening

Conversation

@austenstone

@austenstone austenstone commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Stacked on #77 (shares action.yml / test-copilot.yml). Base retargets to main once #77 merges.

The bug this started from

copilot-labeler.yml has been silently broken in production. It declares explicit permissions but omits copilot-requests: write:

permissions:
  contents: 'read'
  issues: 'write'
  pull-requests: 'read'

Its most recent run is green. The log is not:

Error: Authentication failed (Request ID: E411:9FE7:4FC743:5744F1:6A872414)

The job passed because fail-on-error defaults to false and the action exited silently on a non-zero exit code. Green check, zero work done, no signal.

Fixes:

  • Added copilot-requests: write to the labeler.
  • The action now emits ::warning:: on any non-zero exit when fail-on-error: false. The job still passes (no behavior break), but the failure becomes visible. This is what would have caught the labeler months ago.

Why the other workflows "worked"

The other 8 workflows had no permissions: block at all, so they inherited the permissive repo default, which happens to include CopilotRequests: write and Contents: write. They worked by accident, at maximum privilege.

That is also the trap: adding a least-privilege block without copilot-requests: write silently breaks the workflow. Every block added here includes it.

Workflow Permissions (all + copilot-requests: write)
actions-report contents: read, actions: read, issues: write
ci-fix contents: write, pull-requests: write, actions: read
comment contents: read, issues: write, pull-requests: write
dependabot-update contents: read, pull-requests: write
pr-review contents: read, pull-requests: write
research contents: read, issues: write
security-triage contents: read, issues: write, security-events: read
usage-report contents: read, issues: write

write-all was pointless and dangerous

permissions: write-all # We need workflow: write

workflows is not a GITHUB_TOKEN permission scope. Pushing changes under .github/workflows/ requires a PAT with the workflow scope, full stop. So write-all never granted the thing the comment says it was there for. It was maximum privilege for zero benefit, on a workflow_run trigger firing for every failed workflow in the repo.

This is confirmed by the auto-generated fix PRs themselves, which report "The CI repair token lacks the workflows permission."

Now scoped to what it actually uses.

Also

  • timeout-minutes on all 36 jobs. Previously only the labeler had one, on workflows running open-ended agentic jobs that can otherwise hang to the 6h default.
  • SHA-pinned the only third-party action: dependabot/fetch-metadata@v2 to @25dd0e34f4fe68f24cc83900b1fe3fe149efef98 (v3.1.0). Everything else is first-party actions/*.
  • actions/checkout to v7 everywhere (was a v5/v6 split). v7 blocks fork-PR checkout under workflow_run and pull_request_target, which is directly relevant to copilot-ci-fix.yml. Supersedes Bump actions/checkout from 5 to 6 #50.

Added after initial review

  • Scoped copilot-ci-fix.yml's workflow_run trigger from workflows: ['*'] to the CI workflow. It was firing the write-privileged repair bot on every workflow completion in the repo — 48 of the last 100 runs were no-op guard skips. Reported independently by the auto-generated optimization report in Workflow Optimization Report — 2026-08-01 #76.

austenstone and others added 2 commits August 20, 2026 10:22
…ions

copilot-labeler.yml declared explicit permissions but omitted
copilot-requests: write, so every run failed with "Authentication failed".
Because fail-on-error defaults to false, the job still reported success and
the workflow has been silently doing nothing. The action now emits a warning
on any non-zero exit so this class of failure is visible.

The other eight workflows had no permissions block at all, inheriting the
permissive repo default (including contents: write). They worked by accident.
Each now declares only what it needs, with copilot-requests: write included so
the explicit block does not reproduce the labeler bug.

copilot-ci-fix.yml used write-all "because we need workflow: write", but
workflows is not a GITHUB_TOKEN permission scope, so write-all never granted
it. It was maximum privilege for no benefit, on a workflow_run trigger that
fires for every failed workflow.

Also adds timeout-minutes to all 36 jobs, SHA-pins the only third-party action
(dependabot/fetch-metadata v2 -> v3.1.0), and moves checkout to v7, which
blocks fork-PR checkout under workflow_run and pull_request_target.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The prompt read github.event.issue.*, which is null on pull_request, so
every PR triage ran against a blank title and body and an empty issue
number. Fall back to github.event.pull_request.* and raise pull-requests
to write, since labeling a PR is now actually reachable.
@austenstone
austenstone force-pushed the austenstone-workflow-hardening branch from e0ad9c5 to be60c7b Compare August 20, 2026 17:22
@austenstone
austenstone changed the base branch from austenstone-action-tune-up to main August 20, 2026 17:22
@austenstone
austenstone merged commit 0ac43d6 into main Aug 20, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant