Diagnosing GitHub Actions GITHUB_TOKEN Permission Failures
A diagnostic guide for GitHub Actions jobs that fail because GITHUB_TOKEN lacks the required repository permissions.
27 Jul 2025, 17:15 UTC

A GitHub Actions job can fail with a permission error even when the workflow YAML looks correct. The usual cause is the GITHUB_TOKEN, the installation access token GitHub automatically injects into every job. Since 2023, GitHub assigns this token granular repository permissions by default, and a workflow or repository setting can silently restrict it further. If a step tries to read repository contents, create a check run, or access a secret without the matching permission, the job stops.
Useful takeaway: check the permissions block in your workflow and the repository-level workflow permission setting before rotating tokens or escalating to support. Most failures are caused by a missing or overly restrictive permission grant, not by a broken action.
Recognize the condition
A job fails during a step that interacts with GitHub APIs. Typical signs include:
- Steps using
actions/checkoutcannot clone or fetch the repository. - A step that creates releases, tags, or check runs receives an authorization error.
- A custom script using
gh apior the GitHub REST API reports that the token lacks required scopes. - The workflow log mentions that the token provided has insufficient permissions for the requested operation.
Cause and diagnostic table
| Observed condition | Likely cause | Where to check |
|---|---|---|
Workflow has no permissions block | Default GITHUB_TOKEN permissions are too restrictive for the step | Workflow YAML, top level or job level |
Workflow declares contents: read but step writes a release | Explicit permission grant omits write access | Workflow YAML permissions block |
| Workflow permissions look correct but job still fails | Repository settings override workflow permissions | Settings > Actions > General > Workflow permissions |
| Job uses a personal access token (PAT) stored in a secret | PAT scopes do not cover the requested API | Settings > Developer settings > Personal access tokens |
| Third-party action fails silently | Action requires a scope not granted to GITHUB_TOKEN | Action documentation and workflow log |
Ordered checks
- Read the failing job log. Open the workflow run in the GitHub UI, expand the failing job, and copy the exact error message. Look for the step name and the API operation that was rejected.
- Inspect the workflow YAML. Search for
permissions:at the workflow level and inside each job. Note which permissions are declared and which are missing. - Match permissions to the action. For example,
actions/checkouttypically needscontents: read. Creating a release or tag typically needscontents: write. Writing check runs needschecks: write. - Check repository-level overrides. Go to Settings > Actions > General. Under Workflow permissions, see whether the repository is set to "Read only" or "Read and write." A repository-level restriction can narrow token permissions regardless of the YAML.
- Enable debug logging temporarily. Add the repository secret
ACTIONS_STEP_DEBUGwith valuetrue, then re-run the job. Review the expanded step logs for more detail about the token and the rejected request. - If a PAT is used, verify its scopes. Open Settings > Developer settings > Personal access tokens, select the token, and confirm it has the scopes the workflow needs, such as
repoorworkflow.
Command example: enable debug logging from your terminal
Run this in a local terminal where the GitHub CLI (gh) is authenticated with write access to the repository:
gh secret set ACTIONS_STEP_DEBUG --body "true" --repo OWNER/REPO
Replace OWNER/REPO with your repository. Required permission: ability to create or update repository secrets. Expected result: the secret is stored with no error message. Risk: debug logging produces more verbose job output; remove the secret after diagnosis so sensitive data is not logged unnecessarily.
Fixes matched to findings
Add or broaden the workflow permissions block
If the check found a missing permission, add an explicit permissions block. Use the least privilege required by the job.
permissions:
contents: read
actions: read
checks: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "build step"
If the job writes artifacts or releases, grant write access at the job level instead of the workflow level:
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- run: gh release create v1.0.0 --target main
env:
GH_TOKEN: ${{ github.token }}
Change the repository default if it overrides the workflow
If repository settings are set to "Read only," change them to "Read and write" or keep "Read only" and add explicit write permissions only to jobs that need them. Explicit job-level permissions override repository defaults for that job.
Use a PAT only when GITHUB_TOKEN cannot meet the requirement
Some cross-repository operations or organization-level API calls are not available to GITHUB_TOKEN. Store the PAT as a repository secret, never hardcode it:
steps:
- name: Call external API
run: gh api /orgs/ORG/repos
env:
GH_TOKEN: ${{ secrets.CUSTOM_PAT }}
Rotate the PAT regularly and grant only the scopes it needs.
When to escalate
Escalate to GitHub Support only after all of the following are true:
- The workflow
permissionsblock includes the scopes the failing step requires. - Repository workflow permissions are not overriding the grant.
- Any PAT in use has the correct scopes and is not expired.
- The error persists on re-run with debug logging enabled.
When opening a support request, include the workflow run ID, the failing step name, the exact error message, and a minimal reproduction. Do not attach logs that contain secrets or token values.
Limitations and practical verification
GitHub changed the default GITHUB_TOKEN permission model in 2023, so older tutorials may describe broader default access than your repository currently receives. Treat version-specific behavior as unverified until you confirm it in your own repository.
To verify a fix, re-run the workflow from the GitHub UI after each change. A successful run confirms the missing permission was the cause. If the job still fails, collect the latest debug log before making further changes.
Changing permissions can expose repository data to workflows that previously could not access it. Apply least privilege: grant contents: write only to jobs that publish releases or tags, and avoid workflow-wide write access when job-level access is sufficient.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.