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
10 changes: 5 additions & 5 deletions .github/workflows/test-quality-sentinel.lock.yml

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

104 changes: 104 additions & 0 deletions pkg/workflow/copilot_requests_tip_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
//go:build integration

package workflow

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/github/gh-aw/pkg/testutil"
)

func TestCopilotRequestsEnableTip(t *testing.T) {
tests := []struct {
name string
content string
expectTip bool
}{
{
name: "copilot engine without copilot-requests emits tip",
content: `---
on: workflow_dispatch
engine: copilot
permissions:
contents: read
---

# Test Workflow
`,
expectTip: true,
},
{
name: "copilot engine with copilot-requests write does not emit tip",
content: `---
on: workflow_dispatch
engine: copilot
permissions:
contents: read
copilot-requests: write
---

# Test Workflow
`,
expectTip: false,
},
{
name: "copilot engine with copilot-requests none does not emit tip",
content: `---
on: workflow_dispatch
engine: copilot
permissions:
contents: read
copilot-requests: none
---

# Test Workflow
`,
expectTip: false,
},
{
name: "non-copilot engine does not emit tip",
content: `---
on: workflow_dispatch
engine: claude
permissions:
contents: read
---

# Test Workflow
`,
expectTip: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := testutil.TempDir(t, "copilot-requests-tip-test")
testFile := filepath.Join(tmpDir, "test-workflow.md")
if err := os.WriteFile(testFile, []byte(tt.content), 0600); err != nil {
t.Fatal(err)
}

var compileErr error
stderrOutput := testutil.CaptureStderr(t, func() {
compiler := NewCompiler()
compiler.SetStrictMode(false)
compileErr = compiler.CompileWorkflow(testFile)
})

if compileErr != nil {
t.Fatalf("Expected compilation to succeed but it failed: %v", compileErr)
}

const tipText = "Tip: set permissions.copilot-requests: write to use GitHub Actions token-based inference"
if tt.expectTip && !strings.Contains(stderrOutput, tipText) {
t.Fatalf("Expected copilot-requests tip in stderr, got:\n%s", stderrOutput)
}
if !tt.expectTip && strings.Contains(stderrOutput, tipText) {
t.Fatalf("Did not expect copilot-requests tip in stderr, got:\n%s", stderrOutput)
}
})
}
}
18 changes: 18 additions & 0 deletions pkg/workflow/permissions_compiler_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,28 @@ Ensure proper audience validation and trust policies are configured.`
fmt.Fprintln(os.Stderr, formatCompilerMessage(markdownPath, "warning", warningMsg))
c.IncrementWarningCount()
}
if shouldEmitCopilotRequestsEnableTip(workflowData, workflowPermissions) {
tipMsg := `Tip: set permissions.copilot-requests: write to use GitHub Actions token-based inference with the Copilot engine instead of a personal access token (COPILOT_GITHUB_TOKEN).`
fmt.Fprintln(os.Stderr, formatCompilerMessage(markdownPath, "info", tipMsg))
}

return workflowPermissions, nil
}

func shouldEmitCopilotRequestsEnableTip(workflowData *WorkflowData, workflowPermissions *Permissions) bool {
if workflowData == nil || workflowPermissions == nil {
return false
}
if workflowData.EngineConfig == nil || workflowData.EngineConfig.ID != "copilot" {
return false
}
if level, exists := workflowPermissions.GetExplicit(PermissionCopilotRequests); exists && level == PermissionNone {
return false
}
level, exists := workflowPermissions.Get(PermissionCopilotRequests)
return !exists || level != PermissionWrite
}

func validateOIDCPermissions(workflowData *WorkflowData, workflowPermissions *Permissions) error {
if workflowData == nil {
return nil
Expand Down
Loading