diff --git a/pkg/workflow/github_app_permissions_validation_test.go b/pkg/workflow/github_app_permissions_validation_test.go index 027a9332e19..a37f7890506 100644 --- a/pkg/workflow/github_app_permissions_validation_test.go +++ b/pkg/workflow/github_app_permissions_validation_test.go @@ -437,6 +437,15 @@ func TestConvertPermissionsToAppTokenFields_GitHubAppOnly(t *testing.T) { "permission-vulnerability-alerts": "read", }, }, + { + name: "discussions permission is NOT mapped (actions/create-github-app-token does not declare permission-discussions)", + permissions: func() *Permissions { + p := NewPermissions() + p.Set(PermissionDiscussions, PermissionWrite) + return p + }(), + absentFields: []string{"permission-discussions"}, + }, { name: "models permission is NOT mapped (no GitHub App equivalent)", permissions: func() *Permissions { diff --git a/pkg/workflow/safe_outputs_app_config.go b/pkg/workflow/safe_outputs_app_config.go index dff060e66f0..5023c4bdac5 100644 --- a/pkg/workflow/safe_outputs_app_config.go +++ b/pkg/workflow/safe_outputs_app_config.go @@ -261,9 +261,14 @@ func convertPermissionsToAppTokenFields(permissions *Permissions) map[string]str if level, ok := permissions.Get(PermissionStatuses); ok { fields["permission-statuses"] = string(level) } - if level, ok := permissions.Get(PermissionDiscussions); ok { - fields["permission-discussions"] = string(level) - } + // Note: PermissionDiscussions ("discussions") is intentionally NOT mapped to "permission-discussions" + // here. The actions/create-github-app-token action does NOT declare "permission-discussions" as a + // supported input (see the generated inputs in its action.yml). Passing an unsupported input would + // be silently ignored, meaning the discussions scope would never be explicitly set. GitHub App + // installation tokens inherit the full set of app-installation permissions by default, so the token + // will have discussions access whenever the GitHub App installation itself was granted that permission. + // Repository-level discussions operations should therefore work without an explicit permission-discussions + // field. // GitHub App-only permissions (not available in GitHub Actions GITHUB_TOKEN). // Use GetExplicit() so that shorthand permissions like "read-all" do not accidentally diff --git a/pkg/workflow/safe_outputs_app_test.go b/pkg/workflow/safe_outputs_app_test.go index 2a8716d88b1..c6b1754ba21 100644 --- a/pkg/workflow/safe_outputs_app_test.go +++ b/pkg/workflow/safe_outputs_app_test.go @@ -111,7 +111,13 @@ Test workflow without safe outputs. assert.Nil(t, workflowData.SafeOutputs, "SafeOutputs should be nil") } -// TestSafeOutputsAppTokenDiscussionsPermission tests that discussions permission is included +// TestSafeOutputsAppTokenDiscussionsPermission tests that discussions permission is handled correctly +// in the GitHub App token minting step. +// +// The actions/create-github-app-token action does NOT declare "permission-discussions" as a supported +// input (its generated action.yml only has "permission-team-discussions" for org-level team discussions). +// Therefore "permission-discussions" must NOT be emitted in the token mint step — the GitHub App +// installation token inherits the app's discussion permission from the installation itself. func TestSafeOutputsAppTokenDiscussionsPermission(t *testing.T) { compiler := NewCompilerWithVersion("1.0.0") @@ -149,7 +155,10 @@ Test workflow with discussions permission. // Convert steps to string for easier assertion stepsStr := strings.Join(job.Steps, "") - // Verify that permission-discussions: write is included in the GitHub App token minting step - assert.Contains(t, stepsStr, "permission-discussions: write", "GitHub App token should include discussions write permission") + // permission-discussions is NOT a valid input to actions/create-github-app-token and must not + // appear in the token mint step. Discussions access is inherited from the GitHub App installation. + assert.NotContains(t, stepsStr, "permission-discussions:", "GitHub App token must not use permission-discussions (not a valid action input)") + // Other explicitly supported permission inputs should still be present assert.Contains(t, stepsStr, "permission-contents: read", "GitHub App token should include contents read permission") + assert.Contains(t, stepsStr, "permission-issues: write", "GitHub App token should include issues write permission (create-discussion falls back to issue)") }