$ cat ~/posts/ci-ai-agent-standing-credential.md
TOOLS 07 Jul 2026 · ~4 min read · 767 words

Your CI's AI agent was running on a standing credential.

Running Copilot CLI in GitHub Actions used to need a stored personal access token; the CLI now reads the run built-in GITHUB_TOKEN, a credential that expires with the job.

Tim Stacey
Tim Stacey
lead quality engineer · @timjstacey
.github/workflows/triage.yml
# The old shape: an AI agent in CI, authenticated by a stored PAT.
jobs:
triage:
steps:
- uses: actions/checkout@v7
- run: copilot -p "triage this issue and open a PR"
env:
GITHUB_TOKEN: ${{ secrets.COPILOT_PAT }} # a standing credential

On July 2, 2026, GitHub changed how Copilot CLI authenticates inside Actions. The changelog says the CLI now reads the workflow’s built-in GITHUB_TOKEN, so it runs with no personal access token. GitHub files the move under removing “the operational and security risks of managing long-lived PATs for automations at scale.” The PAT that step used to carry is the failure worth looking at.

The stored PAT is a standing credential

A classic PAT carries one user’s account access. Drop it into secrets.COPILOT_PAT and the CI job acts as that user. GitHub’s own docs on PATs note a token does not expire unless you set an expiry, so it sits in the repository as a credential every job on the branch can read. Rotate it and every automation wired to the same token breaks at once. A fine-grained PAT narrows the scopes, and it is still a person’s credential living past the one job that spends it.

The blast radius grows with the agent. A linter reads code. An agent that triages issues and opens pull requests writes. Hand that agent a broad, long-lived token and a prompt-injection payload in an issue body has a real credential to spend. GitHub names the class in the changelog: managing long-lived PATs for automations at scale is the cost the new default removes.

The token GitHub mints for the run

GitHub already issues a credential scoped to the job. At the start of every workflow run it mints an installation access token and exposes it as GITHUB_TOKEN. The automatic token authentication docs spell out its bounds: the token is scoped to the one repository the workflow runs in, its permissions come from the permissions: key you declare in the workflow, and it expires when the job finishes or after 24 hours, whichever comes first.

Set against a stored PAT, the run token inverts the three properties that made the PAT a liability. It belongs to the run instead of a person, it dies when the job ends, and it carries only the scope you wrote in permissions:. A leaked GITHUB_TOKEN is worthless the moment the job stops.

Wiring Copilot CLI to the run token

Add copilot-requests: write to the job’s permissions and Copilot CLI authenticates with the built-in token. The changelog confirms no PAT and no extra secret to store or rotate.

.github/workflows/triage.yml
jobs:
triage:
permissions:
contents: read
issues: write
pull-requests: write
copilot-requests: write # lets the CLI spend AI credits on the run token
steps:
- uses: actions/checkout@v7
- run: copilot -p "triage this issue and open a PR"
# no env: GITHUB_TOKEN, the CLI reads the run's own token

Two setup facts come with it. Run copilot update or npm install -g @github/copilot first, since the token path needs a recent CLI. And in an org-owned repository the AI credits the CLI spends bill to the organization. GitHub gates that behind the “Allow use of Copilot CLI billed to the organization” policy, which is on by default when the “Copilot CLI” policy is enabled.

What the run token does not fix

The run token shrinks the credential. It does not shrink the agent.

Give the token issues: write and pull-requests: write and the agent can still open a pull request you never asked for, off a poisoned issue. Scope the permissions: block to the job’s real need and treat the prompt as untrusted input, the same way you treat fork code. The ephemeral token caps the damage at one run. It does not decide what the run may do.

The run token is also repo-scoped. An automation that reaches into a second repository cannot ride GITHUB_TOKEN there. That job needs a credential scoped to both repos, and a GitHub App installation token fits it better than a user PAT: it belongs to an app, carries its own permissions, and expires on its own. Reach for a PAT last, not first.

The rule that survives this release is older than the release. A credential in CI should belong to the job, carry only the permissions the job needs, and expire when the job ends. Copilot CLI reading GITHUB_TOKEN makes that the default for one common case. Audit the rest of your workflows for the secrets.SOMETHING_PAT still standing there, and swap each one for a token that dies with its run.

$ echo "EOF · thanks for reading"
Tim Stacey
Written by
Tim Stacey
Lead quality engineer. Writes about testing strategy.