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
2 changes: 0 additions & 2 deletions .github/workflows/ci-doctor.lock.yml

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

8 changes: 8 additions & 0 deletions pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion pkg/workflow/add_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Comment on lines +117 to +124
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standalone add_comment job now conditionally drops discussions:write based on safe-outputs.add-comment.discussions, but there isn't test coverage asserting the generated job permissions for the default vs discussions:false cases (existing tests for buildCreateOutputAddCommentJob only check needs/env). Add a unit test that verifies the job's rendered permissions include/exclude discussions: write accordingly.

Copilot uses AI. Check for mistakes.

// Use the shared builder function to create the job
return c.buildSafeOutputJob(data, SafeOutputJobConfig{
JobName: "add_comment",
Expand All @@ -121,7 +131,7 @@ func (c *Compiler) buildCreateOutputAddCommentJob(data *WorkflowData, mainJobNam
MainJobName: mainJobName,
CustomEnvVars: customEnvVars,
Script: getAddCommentScript(),
Permissions: NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite(),
Permissions: permissions,
Outputs: outputs,
Condition: jobCondition,
Needs: needs,
Expand Down
1 change: 1 addition & 0 deletions pkg/workflow/hide_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Comment on lines 13 to 15
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HideCommentConfig now has a Discussions field, but parseHideCommentConfig never reads discussions from the hide-comment config map. As a result, safe-outputs.hide-comment.discussions: false will be silently ignored and the workflow will still request discussions:write. Parse and set this field (only when the key is present) so the opt-out works as intended.

Copilot uses AI. Check for mistakes.

// parseHideCommentConfig handles hide-comment configuration
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/js/safe_outputs_tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -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": [
Expand Down
16 changes: 14 additions & 2 deletions pkg/workflow/safe_outputs_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio
}
if safeOutputs.AddComments != nil {
safeOutputsPermissionsLog.Print("Adding permissions for add-comment")
permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite())
// 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")
Expand Down Expand Up @@ -97,7 +103,13 @@ func computePermissionsForSafeOutputs(safeOutputs *SafeOutputsConfig) *Permissio
}
if safeOutputs.HideComment != nil {
safeOutputsPermissionsLog.Print("Adding permissions for hide-comment")
permissions.Merge(NewPermissionsContentsReadIssuesWritePRWriteDiscussionsWrite())
// 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")
Expand Down
32 changes: 30 additions & 2 deletions pkg/workflow/safe_outputs_permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) {
},
},
{
name: "add-comment includes all write permissions including discussions",
name: "add-comment default - includes discussions permission",
safeOutputs: &SafeOutputsConfig{
AddComments: &AddCommentsConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1},
Expand All @@ -86,7 +86,21 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) {
},
},
{
name: "hide-comment includes all write permissions including discussions",
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},
Expand All @@ -99,6 +113,20 @@ func TestComputePermissionsForSafeOutputs(t *testing.T) {
PermissionDiscussions: PermissionWrite,
},
},
{
name: "hide-comment with discussions:false - no discussions permission",
safeOutputs: &SafeOutputsConfig{
HideComment: &HideCommentConfig{
BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1},
Discussions: ptrBool(false),
},
},
expected: map[PermissionScope]PermissionLevel{
PermissionContents: PermissionRead,
PermissionIssues: PermissionWrite,
PermissionPullRequests: PermissionWrite,
},
},
{
name: "add-labels only - no discussions permission",
safeOutputs: &SafeOutputsConfig{
Expand Down
Loading