From 32d3dfea40f9b4bc95c9c6874deda1ff7708010a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:44:00 +0000 Subject: [PATCH 1/3] Initial plan From 51a7a72c8e54a792870a2af0ef75648fdca0bcc4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 01:57:42 +0000 Subject: [PATCH 2/3] fix: block unsupported CLAUDE_CODE_OAUTH_TOKEN for Claude run/compile Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- docs/src/content/docs/setup/quick-start.mdx | 4 +- pkg/cli/claude_oauth_token_validation.go | 37 +++++++++++++++ pkg/cli/claude_oauth_token_validation_test.go | 45 +++++++++++++++++++ pkg/cli/compile_pipeline.go | 6 +++ pkg/cli/run_workflow_execution.go | 6 +++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/claude_oauth_token_validation.go create mode 100644 pkg/cli/claude_oauth_token_validation_test.go diff --git a/docs/src/content/docs/setup/quick-start.mdx b/docs/src/content/docs/setup/quick-start.mdx index aeac2d9863a..e59a99eefc1 100644 --- a/docs/src/content/docs/setup/quick-start.mdx +++ b/docs/src/content/docs/setup/quick-start.mdx @@ -78,13 +78,13 @@ The wizard generates a compiled workflow file (`.lock.yml`) automatically — yo > **Setting up `COPILOT_GITHUB_TOKEN`?** > 1. [Create a fine-grained Personal Access Token (PAT)](https://github.com/settings/personal-access-tokens/new) under your user account. > 2. Under **Permissions → Account permissions**, set **Copilot Requests** to **Read**, then generate the token. -> 3. Add it as a repository secret from your repository root with `gh secret set COPILOT_GITHUB_TOKEN < /path/to/token.txt`, or use the GitHub UI. See [Authentication](/gh-aw/reference/auth/#copilot_github_token) for more detail. +> 3. Add it as a repository secret from your repository root with `gh aw secrets set COPILOT_GITHUB_TOKEN --value "YOUR_COPILOT_PAT"`, or use the GitHub UI. See [Authentication](/gh-aw/reference/auth/#copilot_github_token) for more detail. > > [!NOTE] > **Setting up `ANTHROPIC_API_KEY`?** > 1. Create an API key in [Anthropic Console](https://console.anthropic.com/settings/keys). -> 2. Add it as a repository secret from your repository root with `gh secret set ANTHROPIC_API_KEY < /path/to/key.txt`, or use the GitHub UI. See [Authentication](/gh-aw/reference/auth/#anthropic_api_key) for more detail. +> 2. Add it as a repository secret from your repository root with `gh aw secrets set ANTHROPIC_API_KEY --value "YOUR_ANTHROPIC_API_KEY"`, or use the GitHub UI. See [Authentication](/gh-aw/reference/auth/#anthropic_api_key) for more detail. > > [!TIP] diff --git a/pkg/cli/claude_oauth_token_validation.go b/pkg/cli/claude_oauth_token_validation.go new file mode 100644 index 00000000000..a050cc757c3 --- /dev/null +++ b/pkg/cli/claude_oauth_token_validation.go @@ -0,0 +1,37 @@ +package cli + +import ( + "fmt" + "os" + "strings" + + "github.com/github/gh-aw/pkg/constants" +) + +const claudeCodeOAuthTokenEnvVar = "CLAUDE_CODE_OAUTH_TOKEN" + +func validateUnsupportedClaudeOAuthTokenForEngine(engine string) error { + if strings.TrimSpace(os.Getenv(claudeCodeOAuthTokenEnvVar)) == "" { + return nil + } + if strings.EqualFold(strings.TrimSpace(engine), string(constants.ClaudeEngine)) { + return fmt.Errorf("%s is not supported for Claude workflows - set ANTHROPIC_API_KEY instead", claudeCodeOAuthTokenEnvVar) + } + return nil +} + +func validateUnsupportedClaudeOAuthTokenForWorkflowFiles(workflowFiles []string, engineOverride string) error { + if err := validateUnsupportedClaudeOAuthTokenForEngine(engineOverride); err != nil { + return err + } + if strings.TrimSpace(os.Getenv(claudeCodeOAuthTokenEnvVar)) == "" { + return nil + } + for _, workflowFile := range workflowFiles { + engine, _, _ := extractEngineConfigFromFile(workflowFile) + if strings.EqualFold(engine, string(constants.ClaudeEngine)) { + return fmt.Errorf("%s is not supported for Claude workflows - set ANTHROPIC_API_KEY instead", claudeCodeOAuthTokenEnvVar) + } + } + return nil +} diff --git a/pkg/cli/claude_oauth_token_validation_test.go b/pkg/cli/claude_oauth_token_validation_test.go new file mode 100644 index 00000000000..cbc34ce6203 --- /dev/null +++ b/pkg/cli/claude_oauth_token_validation_test.go @@ -0,0 +1,45 @@ +package cli + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestValidateUnsupportedClaudeOAuthTokenForEngine(t *testing.T) { + t.Setenv(claudeCodeOAuthTokenEnvVar, "") + if err := validateUnsupportedClaudeOAuthTokenForEngine("claude"); err != nil { + t.Fatalf("expected no error when token is unset, got %v", err) + } + + t.Setenv(claudeCodeOAuthTokenEnvVar, "gho_test") + if err := validateUnsupportedClaudeOAuthTokenForEngine("copilot"); err != nil { + t.Fatalf("expected no error for non-claude engine, got %v", err) + } + if err := validateUnsupportedClaudeOAuthTokenForEngine("claude"); err == nil || !strings.Contains(err.Error(), "set ANTHROPIC_API_KEY instead") { + t.Fatalf("expected guidance error for claude engine, got %v", err) + } +} + +func TestValidateUnsupportedClaudeOAuthTokenForWorkflowFiles(t *testing.T) { + t.Setenv(claudeCodeOAuthTokenEnvVar, "gho_test") + + tempDir := t.TempDir() + copilotWorkflow := filepath.Join(tempDir, "copilot.md") + claudeWorkflow := filepath.Join(tempDir, "claude.md") + + if err := os.WriteFile(copilotWorkflow, []byte("---\nengine: copilot\n---\n"), 0o644); err != nil { + t.Fatalf("failed to write copilot workflow: %v", err) + } + if err := os.WriteFile(claudeWorkflow, []byte("---\nengine: claude\n---\n"), 0o644); err != nil { + t.Fatalf("failed to write claude workflow: %v", err) + } + + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{copilotWorkflow}, ""); err != nil { + t.Fatalf("expected no error for non-claude workflow, got %v", err) + } + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{claudeWorkflow}, ""); err == nil || !strings.Contains(err.Error(), claudeCodeOAuthTokenEnvVar) { + t.Fatalf("expected unsupported token error for claude workflow, got %v", err) + } +} diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index 84396ccf4d7..38b3042fac4 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -102,6 +102,9 @@ func compileSpecificFiles( continue } compileOrchestrationLog.Printf("Resolved to: %s", resolvedFile) + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{resolvedFile}, config.EngineOverride); err != nil { + return workflowDataList, err + } // Update result with resolved file name result.Workflow = filepath.Base(resolvedFile) @@ -276,6 +279,9 @@ func compileAllFilesInDirectory( if len(mdFiles) == 0 { return nil, fmt.Errorf("no workflow markdown files found in %s (workflow files must start with a frontmatter opener on the first line)", workflowsDir) } + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles(mdFiles, config.EngineOverride); err != nil { + return nil, err + } compileOrchestrationLog.Printf("Found %d markdown files to compile", len(mdFiles)) if config.Verbose { diff --git a/pkg/cli/run_workflow_execution.go b/pkg/cli/run_workflow_execution.go index c6bb5231cac..2c9f3cb94c4 100644 --- a/pkg/cli/run_workflow_execution.go +++ b/pkg/cli/run_workflow_execution.go @@ -70,6 +70,9 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO if workflowIdOrName == "" { return errors.New("workflow name or ID is required") } + if err := validateUnsupportedClaudeOAuthTokenForEngine(opts.EngineOverride); err != nil { + return err + } // Validate input format early before attempting workflow validation for _, input := range opts.Inputs { @@ -108,6 +111,9 @@ func RunWorkflowOnGitHub(ctx context.Context, workflowIdOrName string, opts RunO // Return error directly without wrapping - it already contains formatted message with suggestions return err } + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{workflowFile}, opts.EngineOverride); err != nil { + return err + } // Check if the workflow is runnable (has workflow_dispatch trigger) runnable, err := IsRunnable(workflowFile) From 5bf73fd64b59bc67a0fdd4bf88592686491bb383 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Jun 2026 02:06:13 +0000 Subject: [PATCH 3/3] fix: improve claude oauth token validation diagnostics Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/claude_oauth_token_validation.go | 31 +++++++++++++++++-- pkg/cli/claude_oauth_token_validation_test.go | 3 ++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/pkg/cli/claude_oauth_token_validation.go b/pkg/cli/claude_oauth_token_validation.go index a050cc757c3..530a468b08b 100644 --- a/pkg/cli/claude_oauth_token_validation.go +++ b/pkg/cli/claude_oauth_token_validation.go @@ -6,6 +6,8 @@ import ( "strings" "github.com/github/gh-aw/pkg/constants" + "github.com/github/gh-aw/pkg/parser" + "github.com/github/gh-aw/pkg/workflow" ) const claudeCodeOAuthTokenEnvVar = "CLAUDE_CODE_OAUTH_TOKEN" @@ -28,10 +30,35 @@ func validateUnsupportedClaudeOAuthTokenForWorkflowFiles(workflowFiles []string, return nil } for _, workflowFile := range workflowFiles { - engine, _, _ := extractEngineConfigFromFile(workflowFile) - if strings.EqualFold(engine, string(constants.ClaudeEngine)) { + usesClaude, err := workflowUsesClaudeEngine(workflowFile) + if err != nil { + return fmt.Errorf("failed to inspect workflow %s for engine configuration: %w", workflowFile, err) + } + if usesClaude { return fmt.Errorf("%s is not supported for Claude workflows - set ANTHROPIC_API_KEY instead", claudeCodeOAuthTokenEnvVar) } } return nil } + +func workflowUsesClaudeEngine(workflowFile string) (bool, error) { + content, err := readWorkflowFileContent(workflowFile) + if err != nil { + return false, err + } + result, err := parser.ExtractFrontmatterFromContent(content) + if err != nil { + return false, err + } + + compiler := &workflow.Compiler{} + engineSetting, engineConfig := compiler.ExtractEngineConfig(result.Frontmatter) + + engine := string(constants.CopilotEngine) + if engineConfig != nil && engineConfig.ID != "" { + engine = engineConfig.ID + } else if engineSetting != "" { + engine = engineSetting + } + return strings.EqualFold(engine, string(constants.ClaudeEngine)), nil +} diff --git a/pkg/cli/claude_oauth_token_validation_test.go b/pkg/cli/claude_oauth_token_validation_test.go index cbc34ce6203..d1fb76f92ad 100644 --- a/pkg/cli/claude_oauth_token_validation_test.go +++ b/pkg/cli/claude_oauth_token_validation_test.go @@ -42,4 +42,7 @@ func TestValidateUnsupportedClaudeOAuthTokenForWorkflowFiles(t *testing.T) { if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{claudeWorkflow}, ""); err == nil || !strings.Contains(err.Error(), claudeCodeOAuthTokenEnvVar) { t.Fatalf("expected unsupported token error for claude workflow, got %v", err) } + if err := validateUnsupportedClaudeOAuthTokenForWorkflowFiles([]string{filepath.Join(tempDir, "missing.md")}, ""); err == nil || !strings.Contains(err.Error(), "failed to inspect workflow") { + t.Fatalf("expected inspection error for missing workflow file, got %v", err) + } }