From 64247f15d8019e83c9c37297e69a1226d4725cbf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:28:41 +0000 Subject: [PATCH 1/4] Initial plan From 3b0a29d499daf08fd815bf82028dcc61dbbf0e1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:33:26 +0000 Subject: [PATCH 2/4] Fix add-comment and hide-comment permissions to not request discussions:write Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/add_comment.go | 2 +- pkg/workflow/safe_outputs_permissions.go | 4 ++-- pkg/workflow/safe_outputs_permissions_test.go | 23 +++++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index ffd52ea953..b22709fc5f 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -121,7 +121,7 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam MainJobName: mainJobName, CustomEnvVars: customEnvVars, Script: getAddCommentScript(), - Permissions: NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite(), + Permissions: NewPermissionsContentsReadIssuesWritePRWrite(), Outputs: outputs, Condition: jobCondition, Needs: needs, diff --git a/pkg/workflow/safe_outputs_permissions.go b/pkg/workflow/safe_outputs_permissions.go index a8f1faf61b..24295bc632 100644 --- a/pkg/workflow/safe_outputs_permissions.go +++ b/pkg/workflow/safe_outputs_permissions.go @@ -30,7 +30,7 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio } if safeOutputs.AddComments != nil { safeOutputsPermissionsLog.Print("Adding permissions for add-comment") - permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite()) + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) } if safeOutputs.CloseIssues != nil { safeOutputsPermissionsLog.Print("Adding permissions for close-issue") @@ -97,7 +97,7 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio } if safeOutputs.HideComment != nil { safeOutputsPermissionsLog.Print("Adding permissions for hide-comment") - permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite()) + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) } if safeOutputs.DispatchWorkflow != nil { safeOutputsPermissionsLog.Print("Adding permissions for dispatch-workflow") diff --git a/pkg/workflow/safe_outputs_permissions_test.go b/pkg/workflow/safe_outputs_permissions_test.go index c570e6deb4..1b15132522 100644 --- a/pkg/workflow/safe_outputs_permissions_test.go +++ b/pkg/workflow/safe_outputs_permissions_test.go @@ -72,7 +72,7 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { }, }, { - name: "add-comment includes all write permissions including discussions", + name: "add-comment only - no discussions permission", safeOutputs: &SafeOutputsConfig{ AddComments: &AddCommentsConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, @@ -82,11 +82,10 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { PermissionContents: PermissionRead, PermissionIssues: PermissionWrite, PermissionPullRequests: PermissionWrite, - PermissionDiscussions: PermissionWrite, }, }, { - name: "hide-comment includes all write permissions including discussions", + name: "hide-comment only - no discussions permission", safeOutputs: &SafeOutputsConfig{ HideComment: &HideCommentConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, @@ -96,7 +95,6 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { PermissionContents: PermissionRead, PermissionIssues: PermissionWrite, PermissionPullRequests: PermissionWrite, - PermissionDiscussions: PermissionWrite, }, }, { @@ -226,6 +224,23 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { PermissionDiscussions: PermissionWrite, }, }, + { + name: "add-comment with create-discussion - includes discussions permission", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + CreateDiscussions: &CreateDiscussionsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, + }, + }, { name: "upload-asset requires contents write", safeOutputs: &SafeOutputsConfig{ From 4e18b0442ebce3b501c91ee8fd4c859258789bf2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 03:37:47 +0000 Subject: [PATCH 3/4] Final validation complete Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/ci-doctor.lock.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index 4d966720d0..b413900223 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -1016,7 +1016,6 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write outputs: @@ -1275,7 +1274,6 @@ jobs: runs-on: ubuntu-slim permissions: contents: read - discussions: write issues: write pull-requests: write timeout-minutes: 15 From 7d34838d5265fcf4928d8386ba32db23c8fbf829 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Feb 2026 04:18:49 +0000 Subject: [PATCH 4/4] Add discussions field to control discussions:write permission for add-comment and hide-comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/schemas/main_workflow_schema.json | 8 +++ pkg/workflow/add_comment.go | 12 ++++- pkg/workflow/hide_comment.go | 1 + pkg/workflow/js/safe_outputs_tools.json | 4 +- pkg/workflow/safe_outputs_permissions.go | 16 +++++- pkg/workflow/safe_outputs_permissions_test.go | 51 ++++++++++++------- 6 files changed, 68 insertions(+), 24 deletions(-) diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index 9409c952d5..1e498f6d26 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -4817,6 +4817,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "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." } }, "additionalProperties": false, @@ -5736,6 +5740,10 @@ "type": "string", "enum": ["spam", "abuse", "off_topic", "outdated", "resolved"] } + }, + "discussions": { + "type": "boolean", + "description": "Controls whether the workflow requests discussions:write permission for hide-comment. Default: true (includes discussions:write). Set to false if your GitHub App lacks Discussions permission to prevent 422 errors during token generation." } }, "additionalProperties": false diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index b22709fc5f..05630953bb 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -23,6 +23,7 @@ type AddCommentsConfig struct { Discussion *bool `yaml:"discussion,omitempty"` // Target discussion comments instead of issue/PR comments. Must be true if present. HideOlderComments bool `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. } // buildCreateOutputAddCommentJob creates the add_comment job @@ -113,6 +114,15 @@ 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 + var permissions *Permissions + if data.SafeOutputs.AddComments.Discussions != nil && !*data.SafeOutputs.AddComments.Discussions { + permissions = NewPermissionsContentsReadIssuesWritePRWrite() + } else { + permissions = NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite() + } + // Use the shared builder function to create the job return c.buildSafeOutputJob(data, SafeOutputJobConfig{ JobName: "add_comment", @@ -121,7 +131,7 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam MainJobName: mainJobName, CustomEnvVars: customEnvVars, Script: getAddCommentScript(), - Permissions: NewPermissionsContentsReadIssuesWritePRWrite(), + Permissions: permissions, Outputs: outputs, Condition: jobCondition, Needs: needs, diff --git a/pkg/workflow/hide_comment.go b/pkg/workflow/hide_comment.go index 694d0e1581..c487a9d70a 100644 --- a/pkg/workflow/hide_comment.go +++ b/pkg/workflow/hide_comment.go @@ -11,6 +11,7 @@ type HideCommentConfig struct { BaseSafeOutputConfig `yaml:",inline"` SafeOutputTargetConfig `yaml:",inline"` AllowedReasons []string `yaml:"allowed-reasons,omitempty"` // List of allowed reasons for hiding 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. } // parseHideCommentConfig handles hide-comment configuration diff --git a/pkg/workflow/js/safe_outputs_tools.json b/pkg/workflow/js/safe_outputs_tools.json index b4b8ce8feb..2d4e304afa 100644 --- a/pkg/workflow/js/safe_outputs_tools.json +++ b/pkg/workflow/js/safe_outputs_tools.json @@ -192,7 +192,7 @@ }, { "name": "add_comment", - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance.", + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission.", "inputSchema": { "type": "object", "required": [ @@ -844,7 +844,7 @@ }, { "name": "hide_comment", - "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID.", + "description": "Hide a comment on a GitHub issue, pull request, or discussion. This collapses the comment and marks it as spam, abuse, off-topic, outdated, or resolved. Use this for inappropriate, off-topic, or outdated comments. The comment_id must be a GraphQL node ID (string like 'IC_kwDOABCD123456'), not a numeric REST API comment ID. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.hide-comment configuration to exclude this permission.", "inputSchema": { "type": "object", "required": [ diff --git a/pkg/workflow/safe_outputs_permissions.go b/pkg/workflow/safe_outputs_permissions.go index 24295bc632..57979abaf7 100644 --- a/pkg/workflow/safe_outputs_permissions.go +++ b/pkg/workflow/safe_outputs_permissions.go @@ -30,7 +30,13 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio } if safeOutputs.AddComments != nil { safeOutputsPermissionsLog.Print("Adding permissions for add-comment") - permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) + // Check if discussions permission should be excluded (discussions: false) + // Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission + if safeOutputs.AddComments.Discussions != nil && !*safeOutputs.AddComments.Discussions { + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) + } else { + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite()) + } } if safeOutputs.CloseIssues != nil { safeOutputsPermissionsLog.Print("Adding permissions for close-issue") @@ -97,7 +103,13 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio } if safeOutputs.HideComment != nil { safeOutputsPermissionsLog.Print("Adding permissions for hide-comment") - permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) + // Check if discussions permission should be excluded (discussions: false) + // Default (nil or true) includes discussions:write for GitHub Apps with Discussions permission + if safeOutputs.HideComment.Discussions != nil && !*safeOutputs.HideComment.Discussions { + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWrite()) + } else { + permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite()) + } } if safeOutputs.DispatchWorkflow != nil { safeOutputsPermissionsLog.Print("Adding permissions for dispatch-workflow") diff --git a/pkg/workflow/safe_outputs_permissions_test.go b/pkg/workflow/safe_outputs_permissions_test.go index 1b15132522..3827f47ba8 100644 --- a/pkg/workflow/safe_outputs_permissions_test.go +++ b/pkg/workflow/safe_outputs_permissions_test.go @@ -72,7 +72,7 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { }, }, { - name: "add-comment only - no discussions permission", + name: "add-comment default - includes discussions permission", safeOutputs: &SafeOutputsConfig{ AddComments: &AddCommentsConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, @@ -82,13 +82,43 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { PermissionContents: PermissionRead, PermissionIssues: PermissionWrite, PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, + }, + }, + { + name: "add-comment with discussions:false - no discussions permission", + safeOutputs: &SafeOutputsConfig{ + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + Discussions: ptrBool(false), + }, + }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + }, + }, + { + name: "hide-comment default - includes discussions permission", + safeOutputs: &SafeOutputsConfig{ + HideComment: &HideCommentConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + }, + expected: map[PermissionScope]PermissionLevel{ + PermissionContents: PermissionRead, + PermissionIssues: PermissionWrite, + PermissionPullRequests: PermissionWrite, + PermissionDiscussions: PermissionWrite, }, }, { - name: "hide-comment only - no discussions permission", + name: "hide-comment with discussions:false - no discussions permission", safeOutputs: &SafeOutputsConfig{ HideComment: &HideCommentConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + Discussions: ptrBool(false), }, }, expected: map[PermissionScope]PermissionLevel{ @@ -224,23 +254,6 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) { PermissionDiscussions: PermissionWrite, }, }, - { - name: "add-comment with create-discussion - includes discussions permission", - safeOutputs: &SafeOutputsConfig{ - AddComments: &AddCommentsConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, - }, - CreateDiscussions: &CreateDiscussionsConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, - }, - }, - expected: map[PermissionScope]PermissionLevel{ - PermissionContents: PermissionRead, - PermissionIssues: PermissionWrite, - PermissionPullRequests: PermissionWrite, - PermissionDiscussions: PermissionWrite, - }, - }, { name: "upload-asset requires contents write", safeOutputs: &SafeOutputsConfig{