Skip to content
Latchkey

GitHub Actions checkout persisted credentials push 403

actions/checkout persists the GITHUB_TOKEN for later git commands. If that token is read-only, a push fails with 403. Retrying will not change the token scope.

What this error means

A push after checkout fails with 403 even though credentials are persisted, because the persisted token lacks contents write.

github-actions
remote: Permission to octo/repo.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/octo/repo/': The requested URL returned error: 403

Diagnose it: what token do you actually have?

Permission failures in Actions are almost never about your repository settings alone. Three things combine: the default GITHUB_TOKEN permission set for the repo or organization, the permissions: block in the workflow, and whether the event is a fork pull request, which downgrades the token to read-only regardless of everything else.

.github/workflows/ci.yml
- name: Show the token scopes actually granted
  run: |
    curl -sI -H "Authorization: Bearer $GITHUB_TOKEN" \
      https://api.github.com/ | grep -i "^x-oauth-scopes\|^x-accepted"
    echo "event: ${{ github.event_name }}"
    echo "fork PR: ${{ github.event.pull_request.head.repo.fork }}"
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Common causes

Persisted token is read-only

checkout persists whatever token it used; if permissions are read-only, the push is denied.

Fork run token

On fork PRs the persisted token cannot push to the base repository.

How to fix it

Grant write or persist a stronger token

  1. Add permissions: contents: write so the persisted token can push.
  2. Or check out with a PAT that has write access.
.github/workflows/ci.yml
permissions:
  contents: write
steps:
  - uses: actions/checkout@v4

Grant the narrowest permission that works

Declaring a permissions: block switches the job from the repository default to exactly what you list, so an incomplete block is a common cause of a new failure right after someone tightened security. List every scope the job needs, not just the one that failed.

.github/workflows/ci.yml
permissions:
  contents: read        # checkout
  packages: write       # push to GHCR
  id-token: write       # OIDC to a cloud provider
  pull-requests: write  # comment on or label a PR
  checks: write         # publish check runs

How to prevent it

  • Set contents: write only on jobs that push.
  • Do not rely on the persisted token for pushes from fork PRs.

Frequently asked questions

What causes GitHub Actions checkout persisted credentials push 403?
There are 2 common causes: persisted token is read-only and fork run token. checkout persists whatever token it used; if permissions are read-only, the push is denied.
How do I fix GitHub Actions checkout persisted credentials push 403?
Grant write or persist a stronger token. Add permissions: contents: write so the persisted token can push.
What does GitHub Actions checkout persisted credentials push 403 actually mean?
A push after checkout fails with 403 even though credentials are persisted, because the persisted token lacks contents write.
How do I stop GitHub Actions checkout persisted credentials push 403 happening again?
Set contents: write only on jobs that push. The prevention section lists 2 changes that keep it from recurring.

Related guides

References

Not every red build is your code. Latchkey repairs the ones that are not, on the runner. Start free → 30-day trial · No credit card