Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/approach-validator.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/ci-doctor.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/cloclo.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/dev.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .github/workflows/necromancer.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions pkg/workflow/compiler_pre_activation_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"maps"
"slices"
"strings"

"github.com/github/gh-aw/pkg/constants"
Expand Down Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions pkg/workflow/label_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Loading