If you are maintaining a repository with strict security defaults or branch protection rules, you have likely encountered this error log during a release pipeline or a PR automation step:
HttpError: Resource not accessible by integration
at /home/runner/work/.../index.js:14:10
...
status: 403
This 403 Forbidden error is the standard response when the ephemeral GITHUB_TOKEN attempts a write operation (creating a release, tagging a commit, or commenting on a PR) but lacks the specific OAuth scope required to execute it.
The Root Cause: Least Privilege Defaults
Historically, the auto-generated GITHUB_TOKEN provided to workflows had read and write access to almost all scopes by default. This was convenient but presented a massive surface area for supply chain attacks. If a third-party action was compromised, it could wipe your repository.
GitHub updated the default setting for new organizations and repositories to Restricted (Read-only). Furthermore, many DevOps teams now enforce this restriction at the organization level (Settings -> Actions -> General -> Workflow permissions).
When your pipeline attempts to:
- Run
semantic-release(requires creating Git tags and Releases). - Run a linter that comments on a PR (requires accessing the Issues/PR API).
It fails because the token generated for that run has only contents: read permissions.
The Fix: Explicit Workflow Permissions
Do not change the repository-wide default back to "Read and write." That defeats the security model. Instead, you must explicitly declare the required scopes within the workflow YAML using the permissions key.
Scenario 1: Automated Releases (Tagging and Releasing)
For pipelines that need to push tags back to the repository and create GitHub Releases, you must grant contents: write.
File: .github/workflows/release.yml
name: Production Release
on:
push:
branches:
- main
# 1. GLOBAL PERMISSIONS
# Explicitly set top-level permissions.
# We default to 'read-all' to ensure least privilege for non-specified scopes.
permissions: read-all
jobs:
semantic-release:
runs-on: ubuntu-latest
# 2. JOB-LEVEL PERMISSIONS (Recommended)
# Overwrite the global permissions for this specific job.
# 'contents: write' allows creating tags and releases.
# 'issues: write' and 'pull-requests: write' are required if the
# release bot needs to comment on issues/PRs indicating a release happened.
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for history analysis
persist-credentials: false # We will use the GITHUB_TOKEN explicitly
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install Dependencies
run: npm ci
- name: Create Release
env:
# The token is automatically available, but usually needs
# to be passed explicitly to tools like semantic-release
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
Scenario 2: PR Comments (Linters and Coverage)
If your workflow runs on pull_request and needs to comment on that PR (e.g., adding a code coverage summary), contents: write is not enough. You need pull-requests: write.
File: .github/workflows/pr-check.yml
name: PR Quality Check
on:
pull_request:
branches: [main]
permissions: read-all
jobs:
test-and-comment:
runs-on: ubuntu-latest
permissions:
# Required to read code
contents: read
# Required to post comments on the PR
pull-requests: write
# Required if you want to update checks/statuses on the commit
checks: write
steps:
- uses: actions/checkout@v4
- name: Run Tests
run: npm test -- --coverage
- name: Comment Coverage
uses: thollander/actions-comment-pull-request@v2
with:
message: |
### Test Coverage Report
Tests passed successfully!
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Explanation
1. The permissions Key
The permissions key modifies the JWT (JSON Web Token) claims associated with the GITHUB_TOKEN. When the workflow starts, GitHubMint checks this YAML definition against the repository's maximum allowed policy. If valid, it provisions the token with exactly those scopes.
2. Specific Scopes
contents: write: Grants access togit push(tags/commits) and the Releases API.pull-requests: write: Grants access to the Pull Request API (comments, reviews, labels).issues: write: Grants access to the Issues API. Note that in GitHub's internal architecture, PRs are technically issues, so sometimesissues: writecovers PR comments, butpull-requests: writeis more explicit and preferred for PR interactions.
3. Protected Branches Nuance
If you apply the code above and still receive an error specifically when pushing to main, check your Branch Protection Rules (Settings -> Branches -> 'main').
If "Restrict who can push to matching branches" is enabled, the GITHUB_TOKEN (acting as the github-actions[bot] user) will be blocked even with contents: write.
Solution for Protected Branches:
- Go to the Branch Protection rule for
main. - In the "Restrict who can push" section, search for and add
github-actions[bot](or the specific app name if using a GitHub App). - Alternatively, if you require bypassing pull request reviews for releases, you must use a Personal Access Token (PAT) belonging to an admin or a GitHub App token, as the default
GITHUB_TOKENcannot bypass branch protection rules regardless of permissions.
Conclusion
The Resource not accessible by integration error is a feature, not a bug. It enforces the principle of least privilege. By explicitly defining permissions in your YAML, you adhere to modern security practices while ensuring your CI/CD pipelines have exactly the access they need—no more, no less.