diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 8de56623d9..4efe9e50fd 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -1149,6 +1149,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1349,6 +1350,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/archie.lock.yml b/.github/workflows/archie.lock.yml index 079474ab18..d3e209f4f8 100644 --- a/.github/workflows/archie.lock.yml +++ b/.github/workflows/archie.lock.yml @@ -982,6 +982,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1145,6 +1146,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index 347c717ec2..43d127fb16 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -970,6 +970,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1130,6 +1131,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 795afc0e8f..2b5df580d6 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1177,6 +1177,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1328,6 +1329,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index a7e7770a5b..c858e63a40 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -964,6 +964,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1062,6 +1063,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index 6a11a56d38..628b78fdf6 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -1178,6 +1178,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1349,6 +1350,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index e7b0725fa7..14b06244a0 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -892,6 +892,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -989,6 +990,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "codex" diff --git a/.github/workflows/dev-hawk.lock.yml b/.github/workflows/dev-hawk.lock.yml index cbdeb6cca6..8fe63277ac 100644 --- a/.github/workflows/dev-hawk.lock.yml +++ b/.github/workflows/dev-hawk.lock.yml @@ -1032,6 +1032,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1163,6 +1164,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/discussion-task-miner.lock.yml b/.github/workflows/discussion-task-miner.lock.yml index 99514b55ad..e4720bff61 100644 --- a/.github/workflows/discussion-task-miner.lock.yml +++ b/.github/workflows/discussion-task-miner.lock.yml @@ -1053,6 +1053,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1225,6 +1226,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index fff8d24a5e..6a1d6f49ec 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -994,6 +994,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1152,6 +1153,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 8765833c91..5966c720af 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -1069,6 +1069,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1234,6 +1235,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index c8a1607cea..c330cd38ea 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -1165,6 +1165,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1337,6 +1338,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "claude" diff --git a/.github/workflows/smoke-agent.lock.yml b/.github/workflows/smoke-agent.lock.yml index f3a4271e59..6d7c71f6dc 100644 --- a/.github/workflows/smoke-agent.lock.yml +++ b/.github/workflows/smoke-agent.lock.yml @@ -1001,6 +1001,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1155,6 +1156,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "codex" diff --git a/.github/workflows/smoke-temporary-id.lock.yml b/.github/workflows/smoke-temporary-id.lock.yml index c0833cefa2..02b17bef95 100644 --- a/.github/workflows/smoke-temporary-id.lock.yml +++ b/.github/workflows/smoke-temporary-id.lock.yml @@ -1068,6 +1068,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1236,6 +1237,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 13e3a93a30..192053fddb 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -971,6 +971,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1123,6 +1124,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index f006d3a517..2fa90039c5 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -1030,6 +1030,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1128,6 +1129,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 07cbf42547..b4a48688ca 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -1147,6 +1147,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -1345,6 +1346,7 @@ jobs: contents: read discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 7b43a6fd09..9ef729733d 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -5023,7 +5023,15 @@ }, "discussions": { "type": "boolean", - "description": "Controls whether the workflow requests discussions:write permission for add-comment. Default: true (includes discussions:write). Set to false if your GitHub App lacks Discussions permission to prevent 422 errors during token generation." + "description": "Controls whether the workflow requests discussions:write permission for add-comment and includes discussions in the event trigger condition. Default: true (includes discussions:write). Set to false if your GitHub App lacks Discussions permission to prevent 422 errors during token generation." + }, + "issues": { + "type": "boolean", + "description": "Controls whether the workflow requests issues:write permission for add-comment and includes issues in the event trigger condition. Default: true (includes issues:write). Set to false to disable issue commenting." + }, + "pull-requests": { + "type": "boolean", + "description": "Controls whether the workflow requests pull-requests:write permission for add-comment and includes pull requests in the event trigger condition. Default: true (includes pull-requests:write). Set to false to disable pull request commenting." } }, "additionalProperties": false, diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index 6a68b19a00..604aabb583 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -24,7 +24,9 @@ type AddCommentsConfig struct { Discussion *bool `yaml:"discussion,omitempty"` // Target discussion comments instead of issue/PR comments. Must be true if present. HideOlderComments *string `yaml:"hide-older-comments,omitempty"` // When true, minimizes/hides all previous comments from the same workflow before creating the new comment AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding older comments (default: all reasons allowed) - Discussions *bool `yaml:"discussions,omitempty"` // When false, excludes discussions:write permission. Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission. + Issues *bool `yaml:"issues,omitempty"` // When false, excludes issues:write permission and issues from event condition. Default (nil or true) includes issues:write. + PullRequests *bool `yaml:"pull-requests,omitempty"` // When false, excludes pull-requests:write permission and PRs from event condition. Default (nil or true) includes pull-requests:write. + Discussions *bool `yaml:"discussions,omitempty"` // When false, excludes discussions:write permission and discussions from event condition. Default (nil or true) includes discussions:write. } // buildCreateOutputAddCommentJob creates the add_comment job @@ -91,14 +93,20 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam // Build job condition with event check if target is not specified jobCondition := BuildSafeOutputType("add_comment") if data.SafeOutputs.AddComments != nil && data.SafeOutputs.AddComments.Target == "" { - eventCondition := BuildOr( - BuildOr( - BuildPropertyAccess("github.event.issue.number"), - BuildPropertyAccess("github.event.pull_request.number"), - ), - BuildPropertyAccess("github.event.discussion.number"), - ) - jobCondition = BuildAnd(jobCondition, eventCondition) + var eventTerms []ConditionNode + if data.SafeOutputs.AddComments.Issues == nil || *data.SafeOutputs.AddComments.Issues { + eventTerms = append(eventTerms, BuildPropertyAccess("github.event.issue.number")) + } + if data.SafeOutputs.AddComments.PullRequests == nil || *data.SafeOutputs.AddComments.PullRequests { + eventTerms = append(eventTerms, BuildPropertyAccess("github.event.pull_request.number")) + } + if data.SafeOutputs.AddComments.Discussions == nil || *data.SafeOutputs.AddComments.Discussions { + eventTerms = append(eventTerms, BuildPropertyAccess("github.event.discussion.number")) + } + if len(eventTerms) > 0 { + eventCondition := &DisjunctionNode{Terms: eventTerms} + jobCondition = BuildAnd(jobCondition, eventCondition) + } } // Build the needs list - always depend on mainJobName, and conditionally on the other jobs @@ -113,15 +121,11 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam needs = append(needs, createPullRequestJobName) } - // Determine permissions based on discussions field - // Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission - // Note: PR comments are issue comments, so only issues:write is needed, not pull_requests:write - var permissions *Permissions - if data.SafeOutputs.AddComments.Discussions != nil && !*data.SafeOutputs.AddComments.Discussions { - permissions = NewPermissionsContentsReadIssuesWrite() - } else { - permissions = NewPermissionsContentsReadIssuesWriteDiscussionsWrite() - } + // Determine permissions based on Issues, PullRequests, and Discussions fields. + // Issues: nil or true → issues:write (default: true) + // PullRequests: nil or true → pull-requests:write (default: true) + // Discussions: nil or true → discussions:write (default: true) + permissions := buildAddCommentPermissions(data.SafeOutputs.AddComments) // Use the shared builder function to create the job return c.buildSafeOutputJob(data, SafeOutputJobConfig{ @@ -191,3 +195,23 @@ func (c *Compiler) parseCommentsConfig(outputMap map[string]any) *AddCommentsCon return &config } + +// buildAddCommentPermissions computes the permissions for the add_comment job based on config. +// Issues: nil or true → issues:write (default: true) +// PullRequests: nil or true → pull-requests:write (default: true) +// Discussions: nil or true → discussions:write (default: true) +func buildAddCommentPermissions(config *AddCommentsConfig) *Permissions { + permMap := map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + } + if config == nil || config.Issues == nil || *config.Issues { + permMap[PermissionIssues] = PermissionWrite + } + if config == nil || config.PullRequests == nil || *config.PullRequests { + permMap[PermissionPullRequests] = PermissionWrite + } + if config == nil || config.Discussions == nil || *config.Discussions { + permMap[PermissionDiscussions] = PermissionWrite + } + return NewPermissionsFromMap(permMap) +} diff --git a/pkg/workflow/safe_outputs_permissions.go b/pkg/workflow/safe_outputs_permissions.go index 3d2610deb5..a5d6114e48 100644 --- a/pkg/workflow/safe_outputs_permissions.go +++ b/pkg/workflow/safe_outputs_permissions.go @@ -30,14 +30,7 @@ func ComputePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio } if safeOutputs.AddComments != nil { safeOutputsPermissionsLog.Print("Adding permissions for add-comment") - // Check if discussions permission should be excluded (discussions: false) - // Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission - // Note: PR comments are issue comments, so only issues:write is needed, not pull_requests:write - if safeOutputs.AddComments.Discussions != nil && !*safeOutputs.AddComments.Discussions { - permissions.Merge(NewPermissionsContentsReadIssuesWrite()) - } else { - permissions.Merge(NewPermissionsContentsReadIssuesWriteDiscussionsWrite()) - } + permissions.Merge(buildAddCommentPermissions(safeOutputs.AddComments)) } if safeOutputs.CloseIssues != nil { safeOutputsPermissionsLog.Print("Adding permissions for close-issue") diff --git a/pkg/workflow/safe_outputs_permissions_test.go b/pkg/workflow/safe_outputs_permissions_test.go index a1da8d8c12..4ba92b5af7 100644 --- a/pkg/workflow/safe_outputs_permissions_test.go +++ b/pkg/workflow/safe_outputs_permissions_test.go @@ -72,12 +72,56 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { }, }, { - name: "add-comment default - includes discussions permission", + name: "add-comment default - includes pull-requests and discussions", safeOutputs: &SafeOutputsConfig{ AddComments: &AddCommentsConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, }, }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, + }, + }, + { + name: "add-comment with discussions:true - includes discussions permission", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + Discussions: ptrBool(true), + }, + }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, + }, + }, + { + name: "add-comment with discussions:false - no discussions permission", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + Discussions: ptrBool(false), + }, + }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + }, + }, + { + name: "add-comment with pull-requests:false - no pull-requests permission", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, + PullRequests: ptrBool(false), + }, + }, expected: map[PermissionScope]PermissionLevel{ PermissionContents: PermissionRead, PermissionIssues: PermissionWrite, @@ -85,16 +129,17 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { }, }, { - name: "add-comment with discussions:false - no discussions permission", + name: "add-comment with issues:false - no issues permission", safeOutputs: &SafeOutputsConfig{ AddComments: &AddCommentsConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, - Discussions: ptrBool(false), + Issues: ptrBool(false), }, }, expected: map[PermissionScope]PermissionLevel{ - PermissionContents: PermissionRead, - PermissionIssues: PermissionWrite, + PermissionContents: PermissionRead, + PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, }, }, {