GitHub Actions workflow was trying to push a commit back to the repo (updating a changelog file) and failed:
remote: Permission to org/repo.git denied to github-actions[bot].
fatal: unable to access 'https://github.com/org/repo/': The requested URL returned error: 403
Root cause:
The default GITHUB_TOKEN permissions were read for contents. Workflows that need to push commits, create releases, or write to the repo need explicit write permission.
Since GitHub tightened defaults in 2022, the default permissions for GITHUB_TOKEN are read-only for most things.
Fix — add permissions to the workflow:
name: Update Changelog
on:
push:
branches: [main]
permissions:
contents: write # ← this line fixes it
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Update changelog
run: |
echo "## $(date +%Y-%m-%d)" >> CHANGELOG.md
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md
git commit -m "chore: update changelog [skip ci]"
git pushPermission reference:
| Permission | What it allows |
|---|---|
contents: read | Read repo files (default) |
contents: write | Push commits, create tags |
pull-requests: write | Create/update PRs |
issues: write | Create/comment on issues |
packages: write | Push to GitHub Packages |
id-token: write | OIDC authentication (AWS, GCP) |
Lesson: Always set the minimum required permissions explicitly in workflows. Don't use repo-wide permission overrides if you can scope it to the specific workflow. The [skip ci] in commit messages prevents infinite loops when bots commit.