Skip to content
ohsudo, Home
CI/CD

GitHub Actions "Resource not accessible by integration": give the token the right permission

Fix GitHub Actions "Resource not accessible by integration" (HTTP 403): add the right permissions to GITHUB_TOKEN, and handle fork pull requests and Dependabot safely.

Intermediate4 min readApplies to GitHub Actions · GitHub.com · GitHub Enterprise Cloud · GITHUB_TOKEN

by OhSudo ··

Part of a learning path:Git for everyday work

A workflow step that talks to GitHub fails:

Error: Resource not accessible by integration
HttpError: Resource not accessible by integration
status: 403

The “integration” is your workflow, using its automatic GITHUB_TOKEN. The token works, and it was refused because it doesn’t have the permission this request needs: commenting on a pull request, adding a label, pushing, creating a release.

It often appears out of the blue on a workflow copied from elsewhere, since new repositories and organizations give that token read-only access by default.

  1. Workflow stepcomment, label, push, release
  2. GITHUB_TOKENread-only by default
  3. GitHub API403 Resource not accessible(breaks here)
The workflow's automatic GITHUB_TOKEN works, but lacks the permission this request needs. New repositories and organizations give it read-only access by default.

TL;DR

Add what the job needs, in the workflow:

jobs:
comment:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

If it still fails on a pull request from a fork, that’s on purpose: see section 3.

1. Which permission?

Look at which step failed, and what it does:

The step Needs
Comments on or labels a pull request pull-requests: write (some endpoints also issues: write)
Comments on or labels an issue issues: write
Pushes commits or tags, creates a release contents: write
Pushes to GitHub Container Registry packages: write
Sets commit statuses statuses: write
Publishes check runs (test reporters) checks: write
Deploys to GitHub Pages pages: write and id-token: write
Uses OIDC, for example to log in to a cloud id-token: write
Uploads code scanning results security-events: write

If you’re not sure, GitHub tells you. On a REST API call, the response header x-accepted-github-permissions lists what the endpoint accepts. Look for it with curl -i or gh api -i.

2. Set it in the workflow

The permissions key can sit at the top, for all jobs, or better, on the job that needs it:

permissions:
contents: read
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- run: gh release create "$GITHUB_REF_NAME" --generate-notes
env:
GH_TOKEN: ${{ github.token }}

Two details catch people out:

  • Setting permissions turns everything you don’t list to none. Add pull-requests: write alone, and a private repository’s actions/checkout can start failing, because contents: read is gone.
  • Avoid permissions: write-all. It works, but a compromised action could then do anything the token can. Ask for the smallest set.

The CI/CD Pipeline Generator writes workflows this way, read-only by default, with packages: write only on the job that pushes an image.

3. Pull requests from forks

Here adding permissions won’t help. On a pull_request event from a fork, the token is always read-only, whatever the workflow says, and repository secrets aren’t available. That’s what stops a stranger’s pull request from changing your repository.

So how do you comment on those pull requests? Split the work in two:

  • A pull_request workflow runs the untrusted code, with no permissions, and uploads the result as an artifact.
  • A workflow_run workflow, which runs from your default branch with write access, reads that artifact and posts the comment.

Warning

You’ll see pull_request_target recommended, since it has a write token even for forks. It’s safe only if you never check out or run the pull request’s code in it. Doing so hands your token and secrets to whoever opened the pull request, and this has compromised real projects.

Dependabot has the same treatment: its pull_request runs get a read-only token, and its own set of secrets, under Settings → Secrets and variables → Dependabot. Its token can be raised with permissions on pull_request_target, or on a workflow triggered another way.

4. Repository and organization settings

Check Settings → Actions → General → Workflow permissions. “Read repository contents and packages permissions” is the default, and “Allow GitHub Actions to create and approve pull requests” is a separate box: without it, creating a pull request fails with a similar message, even with pull-requests: write.

An organization can lock these settings, in which case they’re greyed out for the repository, and an organization owner has to change them. permissions in a workflow can’t go above what they allow.

5. When GITHUB_TOKEN isn’t enough

The token only reaches its own repository. To open issues in another one, read a private repository or a private package elsewhere, use a GitHub App token (actions/create-github-app-token), or a fine-grained personal access token, stored as a secret:

- run: gh issue create --repo acme/other --title "Build failed" --body "See the run"
env:
GH_TOKEN: ${{ secrets.OTHER_REPO_TOKEN }}

And an action might not use the token you think: check its token input.

Shell, we have a problem!

Similar, but different: Permission to acme/app.git denied to github-actions[bot] is the same problem during a git push: add contents: write. Resource not accessible by personal access token is the same refusal for a fine-grained token: add the permission to the token. Not Found (404) on a private resource often means the token can’t see it at all. For image builds that fail, see failed to compute cache key. On GitLab, a job waiting forever is This job is stuck.

Found this useful? Share it.

Found a mistake? Let us know at contact@ohsudo.com.

Tools

Try it yourself

Articles

Related articles