con un clic
github-actions-permissions
// Correct GitHub Actions permission scoping for PR and issue operations
// Correct GitHub Actions permission scoping for PR and issue operations
| name | github-actions-permissions |
| description | Correct GitHub Actions permission scoping for PR and issue operations |
| domain | ci-cd, github-actions |
| confidence | high |
| source | earned — discovered via workflow debugging |
GitHub Actions workflows require explicit permissions when using GITHUB_TOKEN to interact with the GitHub API. The permission model is namespace-based and follows GitHub's API structure, which has a critical quirk: pull requests use the issues API namespace for labels, assignees, and milestones.
This skill applies when:
Common misconception: PR operations need pull-requests: write
Reality: Most PR metadata operations need issues: write
# ✅ CORRECT — For adding labels, assignees, or milestones to PRs
permissions:
issues: write
contents: read
# ❌ INCORRECT — Will fail when calling issues.* APIs
permissions:
pull-requests: write
contents: read
| Operation | API Namespace | Required Permission |
|---|---|---|
| Add label to PR | issues.addLabels() | issues: write |
| Assign PR | issues.addAssignees() | issues: write |
| Add milestone to PR | issues.addMilestone() | issues: write |
| Comment on PR | issues.createComment() | issues: write |
| Request review | pulls.requestReviewers() | pull-requests: write |
| Merge PR | pulls.merge() | pull-requests: write |
| Convert to draft | pulls.update() | pull-requests: write |
When building label automation, follow this dependency chain:
Label sync workflow (authoritative source)
issues: write permissionPR metadata workflow (consumer)
issues: write permissionAnti-pattern: Having PR workflow create labels on-the-fly → leads to inconsistent colors/descriptions
Correct pattern: Label sync creates/updates, PR workflow applies
name: PR Squad Metadata
on:
pull_request:
types: [opened, reopened]
permissions:
issues: write # Required for labels and assignees
contents: read # Required for checkout
jobs:
label-and-assign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Add label and assign
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
// Add label (uses issues API)
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number, # Note: "issue_number" for PRs
labels: ['enhancement']
});
// Assign (uses issues API)
await github.rest.issues.addAssignees({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
assignees: ['maintainer']
});
name: Sync Squad Labels
on:
workflow_dispatch:
push:
paths: ['.squad/team.md']
permissions:
issues: write # Required for label creation/updates
contents: read # Required for checkout
jobs:
sync-labels:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create or update labels
uses: actions/github-script@v7
with:
script: |
const labels = [
{ name: 'enhancement', color: 'A2EEEF', description: 'New feature or improvement' },
{ name: 'bug', color: 'FF0422', description: 'Something isn\'t working' }
];
for (const label of labels) {
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name
});
// Exists — update
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
} catch (err) {
if (err.status === 404) {
// Doesn't exist — create
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description
});
}
}
}
# This will FAIL when calling issues.addLabels()
permissions:
pull-requests: write
contents: read
Error message you'll see:
Resource not accessible by integration
# Bad: Every PR creates its own label (inconsistent colors, no source of truth)
await github.rest.issues.createLabel({
name: 'enhancement',
color: 'RANDOM_COLOR' # Different each time!
});
Correct approach: Separate label sync workflow as authoritative source
# Bad: Using a literal number
issue_number: 42
# Good: Using the PR number from context
issue_number: context.payload.pull_request.number
Debugging permission errors:
issues.* vs pulls.*)permissions: block to match the API namespaceRule of thumb:
github.rest.issues.* → need issues: writegithub.rest.pulls.* → need pull-requests: writeissues: writecore.warning() instead of failing.github/workflows/ and .squad/templates/workflows/ in sync