# GitHub Actions Permission Denied: Complete Fix Guide

You're running a GitHub Actions workflow and suddenly hit a wall—permission denied. This error appears in many forms and each has a different solution.

Let me walk through every type of permission error you'll encounter in GitHub Actions and how to fix each one.

Understanding GitHub Actions Permissions

GitHub Actions has multiple layers of permissions:

  1. 1.Repository permissions - What the workflow can do in the repo
  2. 2.Workflow permissions - GITHUB_TOKEN scope settings
  3. 3.Environment permissions - Protection rules for deployments
  4. 4.File system permissions - Linux user permissions on the runner

Understanding which layer is causing your issue is the first step to fixing it.

Fix 1: GITHUB_TOKEN Permission Denied on Push

You try to push changes in your workflow and get:

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

Cause: The default GITHUB_TOKEN has restricted permissions for security.

Solution A: Update workflow permissions

Go to your repository settings:

  1. 1.Navigate to Settings → Actions → General
  2. 2.Scroll to "Workflow permissions"
  3. 3.Change to "Read and write permissions"
  4. 4.Save

Solution B: Set permissions in the workflow file:

```yaml name: Build and Deploy

on: push

permissions: contents: write # Required for pushing changes pull-requests: write # Required for creating PRs

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }}

  • name: Make changes and push
  • run: |
  • git config user.name "github-actions[bot]"
  • git config user.email "github-actions[bot]@users.noreply.github.com"
  • echo "Update" >> file.txt
  • git add file.txt
  • git commit -m "Automated update"
  • git push
  • `

Solution C: Use a Personal Access Token (PAT)

For more privileged operations:

yaml
steps:
  - uses: actions/checkout@v4
    with:
      token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}  # Create in GitHub Settings → Developer settings → Personal access tokens

Fix 2: Permission Denied Creating Pull Requests

When your workflow tries to create a PR:

bash
Error: Resource not accessible by integration (GH209)

Cause: GITHUB_TOKEN doesn't have pull-request write permission.

Solution:

```yaml name: Create PR

on: push

permissions: contents: write pull-requests: write

jobs: create-pr: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  • name: Create Pull Request
  • uses: peter-evans/create-pull-request@v5
  • with:
  • token: ${{ secrets.GITHUB_TOKEN }}
  • branch: automated-changes
  • title: "Automated Changes"
  • body: "Changes made by workflow"
  • `

Fix 3: Permission Denied Accessing Other Repositories

When your workflow tries to access another private repository:

bash
fatal: repository 'https://github.com/org/private-repo.git/' not found

Cause: GITHUB_TOKEN only works within its own repository.

Solution:

Create a PAT with access to the target repository:

yaml
steps:
  - name: Checkout private repo
    uses: actions/checkout@v4
    with:
      repository: org/private-repo
      token: ${{ secrets.ORG_PAT }}
      path: private-repo

For organization-wide access, use a GitHub App or deploy key:

yaml
steps:
  - name: Checkout with deploy key
    uses: actions/checkout@v4
    with:
      repository: org/private-repo
      ssh-key: ${{ secrets.DEPLOY_KEY }}

Fix 4: File System Permission Denied

Errors like:

bash
Error: EACCES: permission denied, open '/home/runner/work/repo/output.json'

Or:

bash
mkdir: cannot create directory '/usr/local/lib/my-lib': Permission denied

Cause: GitHub Actions runs as a non-root user (runner), and some operations require root.

Solution for npm global installs:

```yaml - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20'

  • name: Install global package
  • run: npm install -g package-name
  • # This may fail if it tries to write to protected directories
  • `

Fix by using sudo:

yaml
- name: Install global package
  run: sudo npm install -g package-name

Or use a local installation:

yaml
- name: Install package locally
  run: |
    npm install package-name
    npx package-name  # Use npx instead of global install

Solution for file ownership:

```yaml - name: Fix permissions run: sudo chown -R $(whoami) /path/to/directory

  • name: Create protected directory
  • run: sudo mkdir -p /usr/local/lib/my-lib && sudo chown -R $(whoami) /usr/local/lib/my-lib
  • `

Solution for running scripts:

```yaml - name: Make script executable run: chmod +x ./scripts/deploy.sh

  • name: Run script
  • run: ./scripts/deploy.sh
  • `

Fix 5: Docker Permission Denied

When working with Docker:

bash
permission denied while trying to connect to the Docker daemon socket

Solution:

GitHub-hosted runners have Docker pre-installed and accessible. If you see this error:

yaml
- name: Fix Docker permissions
  run: sudo chmod 666 /var/run/docker.sock

For Docker registry push failures:

bash
denied: permission_denied: write access to repository

Solution:

```yaml - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }}

  • name: Build and push
  • uses: docker/build-push-action@v5
  • with:
  • context: .
  • push: true
  • tags: ${{ secrets.DOCKERHUB_USERNAME }}/image:latest
  • `

For GitHub Container Registry:

```yaml - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}

  • name: Build and push to GHCR
  • uses: docker/build-push-action@v5
  • with:
  • context: .
  • push: true
  • tags: ghcr.io/${{ github.repository_owner }}/image:latest
  • `

Fix 6: AWS/GCP/Azure Permission Denied

Cloud deployment permission errors:

bash
Error: AccessDeniedException: User is not authorized to perform: s3:PutObject

Solution for AWS:

```yaml - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1

  • name: Deploy to S3
  • run: aws s3 sync ./dist s3://my-bucket/
  • `

For OIDC (recommended for security):

```yaml permissions: id-token: write contents: read

jobs: deploy: runs-on: ubuntu-latest steps: - name: Configure AWS credentials with OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1 ```

Solution for GCP:

```yaml - name: Authenticate to Google Cloud uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_SA_KEY }}

  • name: Deploy to GKE
  • uses: google-github-actions/get-gke-credentials@v2
  • with:
  • cluster_name: my-cluster
  • location: us-central1
  • `

Fix 7: Environment Protection Rules

When deploying to protected environments:

bash
Error: Deployment to environment 'production' is blocked by protection rules

Cause: Environment has protection rules requiring approvals.

Solution:

  1. 1.Go to Settings → Environments
  2. 2.Click on the environment (e.g., "production")
  3. 3.Review protection rules:
  4. 4.- Required reviewers
  5. 5.- Wait timer
  6. 6.- Deployment branches

Options: - Add yourself as a required reviewer - Configure the allowed branches - Use a different environment for testing

yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Protected environment
    steps:
      - name: Deploy
        run: ./deploy.sh

For bypassing in specific cases, use conditions:

yaml
deploy-production:
  if: github.ref == 'refs/heads/main'
  runs-on: ubuntu-latest
  environment: production
  steps:
    - name: Deploy
      run: ./deploy.sh

Permission Levels Reference

PermissionAccess LevelUse Case
contents: readRead repositoryCheckout code
contents: writeWrite repositoryPush changes, releases
packages: readRead packagesInstall from GHCR
packages: writeWrite packagesPublish to GHCR
pull-requests: writeWrite PRsCreate/update PRs
deployments: writeWrite deploymentsCreate deployments
checks: writeWrite checksUpdate check status
id-token: writeOIDC tokensCloud authentication

Debugging Permission Issues

Add a debug step to check token permissions:

yaml
- name: Debug permissions
  run: |
    echo "GITHUB_TOKEN permissions:"
    curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
      https://api.github.com/repos/${{ github.repository }} \
      | jq '.permissions'
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

This shows what permissions your token actually has.