From 7a24dfee430cde85ff98a87bb577b8cecd806039 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:17:39 +0000 Subject: [PATCH 1/2] Initial plan From e07b874b8b719c3f61020bae6b89cd584ad3d56b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 16:27:04 +0000 Subject: [PATCH 2/2] Fix projects toolset warning to only show for explicit usage, not implicit 'all' Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com> --- pkg/workflow/compiler.go | 6 +- pkg/workflow/projects_toolset_warning_test.go | 169 ++++++++++++++++++ 2 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 pkg/workflow/projects_toolset_warning_test.go diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index b1d31b601d..a8947b5820 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -356,8 +356,10 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath return errors.New(formattedErr) } - // Print informational message if "projects" toolset is used - for _, toolset := range enabledToolsets { + // Print informational message if "projects" toolset is explicitly specified + // (not when implied by "all", as users unlikely intend to use projects with "all") + originalToolsets := workflowData.ParsedTools.GitHub.Toolset.ToStringSlice() + for _, toolset := range originalToolsets { if toolset == "projects" { fmt.Fprintln(os.Stderr, console.FormatInfoMessage("The 'projects' toolset requires a GitHub token with organization Projects permissions.")) fmt.Fprintln(os.Stderr, console.FormatInfoMessage("See: https://githubnext.github.io/gh-aw/reference/tokens/#gh_aw_project_github_token-github-projects-v2")) diff --git a/pkg/workflow/projects_toolset_warning_test.go b/pkg/workflow/projects_toolset_warning_test.go new file mode 100644 index 0000000000..c8c84e9f7e --- /dev/null +++ b/pkg/workflow/projects_toolset_warning_test.go @@ -0,0 +1,169 @@ +package workflow + +import ( + "bytes" + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/githubnext/gh-aw/pkg/stringutil" + "github.com/githubnext/gh-aw/pkg/testutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProjectsToolsetWarning(t *testing.T) { + tests := []struct { + name string + workflowMD string + shouldWarn bool + warningContains string + }{ + { + name: "Explicit projects toolset should warn", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [projects] +--- + +# Test Workflow + +This workflow explicitly uses projects toolset. +`, + shouldWarn: true, + warningContains: "The 'projects' toolset requires a GitHub token", + }, + { + name: "Explicit projects with other toolsets should warn", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [repos, issues, projects] +--- + +# Test Workflow + +This workflow uses projects among other toolsets. +`, + shouldWarn: true, + warningContains: "The 'projects' toolset requires a GitHub token", + }, + { + name: "All toolset should NOT warn about projects", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [all] +--- + +# Test Workflow + +This workflow uses all toolsets (implicit projects). +`, + shouldWarn: false, + }, + { + name: "Default toolsets should NOT warn", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [default] +--- + +# Test Workflow + +This workflow uses default toolsets. +`, + shouldWarn: false, + }, + { + name: "No projects toolset should NOT warn", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [repos, issues, pull_requests] +--- + +# Test Workflow + +This workflow does not use projects toolset. +`, + shouldWarn: false, + }, + { + name: "All with other toolsets should NOT warn", + workflowMD: `--- +on: push +engine: copilot +tools: + github: + toolsets: [all, repos] +--- + +# Test Workflow + +This workflow uses all with redundant repos. +`, + shouldWarn: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create temporary directory for test + tempDir := testutil.TempDir(t, "test-*") + mdPath := filepath.Join(tempDir, "test-workflow.md") + + // Write workflow file + err := os.WriteFile(mdPath, []byte(tt.workflowMD), 0644) + require.NoError(t, err, "Failed to write test workflow") + + // Capture stderr + oldStderr := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + + // Compile the workflow + compiler := NewCompiler(false, "", "test") + compileErr := compiler.CompileWorkflow(mdPath) + require.NoError(t, compileErr, "Failed to compile workflow") + + // Restore stderr and read captured output + w.Close() + os.Stderr = oldStderr + var buf bytes.Buffer + io.Copy(&buf, r) + stderrOutput := buf.String() + + // Read the generated YAML to verify it was created successfully + yamlPath := stringutil.MarkdownToLockFile(mdPath) + _, err = os.ReadFile(yamlPath) + require.NoError(t, err, "Failed to read generated YAML") + + // Check if warning was shown + hasWarning := strings.Contains(stderrOutput, "projects' toolset requires") + + if tt.shouldWarn { + assert.True(t, hasWarning, "Expected warning about projects toolset, but none found.\nStderr output:\n%s", stderrOutput) + if tt.warningContains != "" { + assert.Contains(t, stderrOutput, tt.warningContains, "Warning message should contain expected text") + } + } else { + assert.False(t, hasWarning, "Expected NO warning about projects toolset, but found one.\nStderr output:\n%s", stderrOutput) + } + }) + } +}