diff --git a/.github/workflows/approach-validator.lock.yml b/.github/workflows/approach-validator.lock.yml index e83a3f107a2..4131f8aeefe 100644 --- a/.github/workflows/approach-validator.lock.yml +++ b/.github/workflows/approach-validator.lock.yml @@ -1739,6 +1739,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read + pull-requests: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 2dcebef0caa..855880dca45 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1850,6 +1850,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read + pull-requests: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: diff --git a/.github/workflows/cloclo.lock.yml b/.github/workflows/cloclo.lock.yml index 848fa3e44d5..717f6446fb4 100644 --- a/.github/workflows/cloclo.lock.yml +++ b/.github/workflows/cloclo.lock.yml @@ -1953,6 +1953,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read + pull-requests: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index c69227a401e..2c550791b44 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -1730,6 +1730,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read + pull-requests: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: diff --git a/.github/workflows/necromancer.lock.yml b/.github/workflows/necromancer.lock.yml index f4ca99b7671..1668bce3afa 100644 --- a/.github/workflows/necromancer.lock.yml +++ b/.github/workflows/necromancer.lock.yml @@ -1753,6 +1753,7 @@ jobs: runs-on: ubuntu-slim permissions: contents: read + pull-requests: read env: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: diff --git a/pkg/workflow/compiler_pre_activation_job.go b/pkg/workflow/compiler_pre_activation_job.go index 1bd60ba561c..51d76b0f683 100644 --- a/pkg/workflow/compiler_pre_activation_job.go +++ b/pkg/workflow/compiler_pre_activation_job.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "maps" + "slices" "strings" "github.com/github/gh-aw/pkg/constants" @@ -98,6 +99,15 @@ func (c *Compiler) buildPreActivationPermissions(data *WorkflowData, setupAction } perms.Set(PermissionActions, PermissionRead) } + // Auto-grant pull-requests: read when label_command uses decentralized strategy + // with pull_request events. The check_membership.cjs script calls the pulls API + // to verify PR provenance, which requires pull-requests: read. + if data.LabelCommandDecentralized && slices.Contains(FilterLabelCommandEvents(data.LabelCommandEvents), "pull_request") { + if perms == nil { + perms = NewPermissions() + } + perms.Set(PermissionPullRequests, PermissionRead) + } // Merge on.permissions into the pre-activation job permissions. // on.permissions lets users declare extra scopes required by their on.steps steps. if data.OnPermissions != nil { diff --git a/pkg/workflow/label_command_test.go b/pkg/workflow/label_command_test.go index 175c76ebbf0..b2ba24dd656 100644 --- a/pkg/workflow/label_command_test.go +++ b/pkg/workflow/label_command_test.go @@ -611,3 +611,39 @@ Run CI diagnostics. require.Contains(t, lockStr, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'pull_request'") require.Contains(t, lockStr, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'ci-doctor'") } + +// TestLabelCommandDecentralizedPreActivationPullRequestsReadPermission verifies that the +// pre_activation job is auto-granted pull-requests: read when label_command uses +// strategy: decentralized with events including pull_request. +// This is required because check_membership.cjs calls the pulls API to verify PR provenance. +func TestLabelCommandDecentralizedPreActivationPullRequestsReadPermission(t *testing.T) { + tempDir := t.TempDir() + workflowContent := `--- +name: Label Command Decentralized PR Permission +on: + label_command: + name: ci-doctor + events: [pull_request] + strategy: decentralized +engine: copilot +--- + +Run CI diagnostics. +` + + workflowPath := filepath.Join(tempDir, "label-command-decentralized-perm.md") + require.NoError(t, os.WriteFile(workflowPath, []byte(workflowContent), 0644)) + + compiler := NewCompiler() + require.NoError(t, compiler.CompileWorkflow(workflowPath)) + + lockFilePath := stringutil.MarkdownToLockFile(workflowPath) + lockContent, err := os.ReadFile(lockFilePath) + require.NoError(t, err) + lockStr := string(lockContent) + + preActivationSection := extractJobSection(lockStr, "pre_activation") + require.NotEmpty(t, preActivationSection, "expected pre_activation job in lock file") + require.Contains(t, preActivationSection, "pull-requests: read", + "pre_activation job should have pull-requests: read for decentralized label_command with pull_request events") +}