GitHub Actions Workflow Failed: Permission to Repository Fix
Your GitHub Actions workflow failed with a permission denied error. Here's the real fix, starting simple.
The 30-Second Fix: Check Your GITHUB_TOKEN Permissions
You're running a GitHub Actions workflow, and it fails with a 403 error, something like "Resource not accessible by integration" or "Permission denied to repository." Nine times out of ten, this is your GITHUB_TOKEN not having the right permissions. Here's the quick check:
Look at your workflow YAML file. If you're using GITHUB_TOKEN (the default token that GitHub creates for your workflow run), the default scope is pretty limited—it can only do read operations on the current repository. For write operations, you need to explicitly set permissions on the token.
Add this block at the top of your workflow, right after the name field:
permissions:
contents: write
pull-requests: write
If you need write access to other stuff like issues or deployments, add those lines too. For example:
permissions: write-all
Had a client last month whose entire build pipeline died because they forgot this. The workflow was trying to push a tag and the token only had read access. Added the permissions block, and it worked instantly.
If your workflow still fails, move on to the next step.
The 5-Minute Fix: Use a Personal Access Token (PAT)
Sometimes the GITHUB_TOKEN just isn't enough—especially if your workflow needs to access another repository, or if you're running protected branch checks. The fix is a Personal Access Token (PAT).
Here's how to set it up:
- Generate a PAT from your GitHub account (Settings > Developer settings > Personal access tokens > Fine-grained tokens or classic tokens). For classic tokens, give it
reposcope. Fine-grained tokens let you limit to specific repos and permissions. - Copy the token. Do not paste it in your YAML file. You'll store it as a secret in your repository (Settings > Secrets and variables > Actions > New repository secret). Name it something like
MY_PAT. - In your workflow, replace
${{ secrets.GITHUB_TOKEN }}with${{ secrets.MY_PAT }}wherever you need to authenticate.
Real example: I had a workflow that pushed to a separate documentation repo from a main code repo. The GITHUB_TOKEN can't cross repos. Switched to a PAT with access to both repos, and the workflow ran without issues.
Common mistake: People copy the PAT directly into the YAML file. Don't. If someone views the file later, they'll see your token. Use secrets.
Still failing? Let's check your workflow's YAML for missing authentication.
If your workflow uses actions/checkout, make sure you're passing the token. Without it, the checkout step might not have write permissions even if the workflow does. Add this:
- name: Checkout code
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
If you're using a PAT, replace GITHUB_TOKEN with your secret name. If you're using multiple tokens across steps, each one needs its own auth.
The 15-Minute Fix: Check Organization and Repository Settings
If the simple fixes didn't work, you're dealing with something deeper—usually an organization policy or a branch protection rule blocking your workflow.
Here's what to check:
1. Organization OAuth App Policy
If your repository belongs to an organization, the org might have disabled third-party access for GitHub Actions. Go to your org's Settings > Third-party access > GitHub Actions. Make sure it's not blocked. If it is, you'll get a 403 even with a PAT.
I've seen this happen when orgs tighten security after a breach. The fix is to ask your org admin to allow GitHub Actions or add your token to the allowlist.
2. Branch Protection Rules
Protected branches can block write operations from tokens that aren't the GITHUB_TOKEN with proper permissions. If you're trying to push to a protected branch (like main), the rule might require: "Include administrators" or "Allow force pushes." Workflows don't count as administrators by default.
Check the branch's protection rules (Settings > Branches). If you need the workflow to push there, you can either disable the rule's override for admins or use a PAT that's stored as an organization secret with admin access.
3. Repository Visibility and Token Scope
If your workflow accesses a private repository but your PAT only has access to public repos, it'll fail. Check the PAT's scope. For fine-grained tokens, also check that the repository is selected in the token's settings.
4. Environment Protection Rules
If your workflow uses environments with required reviewers, the token might not pass the approval gate. You'll see a 403 with a message like "Waiting for review." The fix: either remove the environment protection or use a PAT from a user who's a reviewer on that environment.
5. Self-Hosted Runners
If you're using self-hosted runners, the runner might not have internet access to reach GitHub's API, or it might be running as a user without proper network permissions. Check the runner's network configuration and the user account running the runner service.
Here's a quick checklist table to diagnose the issue:
| Symptom | Likely Fix | Time |
|---|---|---|
| 403 on push to same repo | Add permissions: contents: write to workflow |
30 seconds |
| 403 on push to different repo | Use a PAT stored as a secret | 5 minutes |
| 403 on all actions in org | Check org's third-party access policy | 15 minutes |
| 403 on protected branch | Adjust branch protection rules or use admin PAT | 10 minutes |
| 403 with environment review message | Remove environment protection or add reviewer | 5 minutes |
If you've tried all this and it's still failing, check GitHub's status page (status.github.com). Rare, but sometimes their API has a hiccup. Had a call from a panicked client once, turns out GitHub was down for 20 minutes. Saved them a lot of head-scratching.
One last thing: If you're using fine-grained PATs, make sure the repository you're trying to access is included in the token's repository selection. You'd be surprised how often people set up a PAT for a specific repo and then try to use it on a different one.
Pro tip: Always test your token's permissions by running a simplecurlcommand in a workflow step before doing the real work. Something like:curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/owner/repo. If that fails, your token's the problem.
That's it. Start with the permissions block, then the PAT, then the deeper settings. Most people fix it in under 5 minutes. The rest are usually organization policies or branch protection gotchas.
Was this solution helpful?