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
3 changes: 3 additions & 0 deletions cmd/gh-aw/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ Examples:
engineOverride, _ := cmd.Flags().GetString("engine")
actionMode, _ := cmd.Flags().GetString("action-mode")
actionTag, _ := cmd.Flags().GetString("action-tag")
actionsRepo, _ := cmd.Flags().GetString("actions-repo")
validate, _ := cmd.Flags().GetBool("validate")
watch, _ := cmd.Flags().GetBool("watch")
dir, _ := cmd.Flags().GetString("dir")
Expand Down Expand Up @@ -312,6 +313,7 @@ Examples:
EngineOverride: engineOverride,
ActionMode: actionMode,
ActionTag: actionTag,
ActionsRepo: actionsRepo,
Validate: validate,
Watch: watch,
WorkflowDir: workflowDir,
Expand Down Expand Up @@ -646,6 +648,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all
compileCmd.Flags().StringP("engine", "e", "", "Override AI engine (claude, codex, copilot, custom)")
compileCmd.Flags().String("action-mode", "", "Action script inlining mode (inline, dev, release). Auto-detected if not specified")
compileCmd.Flags().String("action-tag", "", "Override action SHA or tag for actions/setup (overrides action-mode to release). Accepts full SHA or tag name")
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The --action-tag flag help text says it always overrides --action-mode to release, but setupActionMode now keeps action mode when the user explicitly sets --action-mode action. Please update this help text (and any related docs/comments) to reflect the new precedence so the CLI help matches actual behavior.

This issue also appears in the following locations of the same file:

  • line 651
  • line 649
Suggested change
compileCmd.Flags().String("action-tag", "", "Override action SHA or tag for actions/setup (overrides action-mode to release). Accepts full SHA or tag name")
compileCmd.Flags().String("action-tag", "", "Override action SHA or tag for actions/setup. When set without --action-mode, defaults action-mode to release. Accepts full SHA or tag name")

Copilot uses AI. Check for mistakes.
compileCmd.Flags().String("actions-repo", "", "Override the external actions repository used in action mode (default: github/gh-aw-actions). Example: --actions-repo myorg/my-aw-actions")
compileCmd.Flags().Bool("validate", false, "Enable GitHub Actions workflow schema validation, container image validation, and action SHA validation")
compileCmd.Flags().BoolP("watch", "w", false, "Watch for changes to workflow files and recompile automatically")
compileCmd.Flags().StringP("dir", "d", "", "Workflow directory (default: .github/workflows)")
Expand Down
23 changes: 19 additions & 4 deletions pkg/cli/compile_compiler_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ func createAndConfigureCompiler(config CompileConfig) *workflow.Compiler {
// Set up action mode
setupActionMode(compiler, config.ActionMode, config.ActionTag)

// Set up actions repository override if specified
if config.ActionsRepo != "" {
compiler.SetActionsRepo(config.ActionsRepo)
compileCompilerSetupLog.Printf("Actions repository overridden: %s (default: %s)", config.ActionsRepo, workflow.GitHubActionsOrgRepo)
}

// Set up repository context
setupRepositoryContext(compiler)

Expand Down Expand Up @@ -153,12 +159,21 @@ func configureCompilerFlags(compiler *workflow.Compiler, config CompileConfig) {
func setupActionMode(compiler *workflow.Compiler, actionMode string, actionTag string) {
compileCompilerSetupLog.Printf("Setting up action mode: %s, actionTag: %s", actionMode, actionTag)

// If actionTag is specified, override to release mode
// If actionTag is specified, it pins the version used in action/release references.
// When --action-mode action is explicitly set alongside --action-tag, honour the explicit
// action mode so that the external actions repo (--actions-repo) is also respected.
// Without an explicit action mode, --action-tag still defaults to release mode (original behaviour).
if actionTag != "" {
compileCompilerSetupLog.Printf("--action-tag specified (%s), overriding to release mode", actionTag)
compiler.SetActionMode(workflow.ActionModeRelease)
compiler.SetActionTag(actionTag)
compileCompilerSetupLog.Printf("Action mode set to: release with tag/SHA: %s", actionTag)
if actionMode == string(workflow.ActionModeAction) {
compileCompilerSetupLog.Printf("--action-tag specified (%s) with --action-mode action, using action mode", actionTag)
compiler.SetActionMode(workflow.ActionModeAction)
compileCompilerSetupLog.Printf("Action mode set to: action with tag/SHA: %s", actionTag)
} else {
compileCompilerSetupLog.Printf("--action-tag specified (%s), overriding to release mode", actionTag)
compiler.SetActionMode(workflow.ActionModeRelease)
compileCompilerSetupLog.Printf("Action mode set to: release with tag/SHA: %s", actionTag)
}
return
}

Expand Down
1 change: 1 addition & 0 deletions pkg/cli/compile_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type CompileConfig struct {
JSONOutput bool // Output validation results as JSON
ActionMode string // Action script inlining mode: inline, dev, or release
ActionTag string // Override action SHA or tag for actions/setup (overrides action-mode to release)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

CompileConfig.ActionTag is documented as always overriding action-mode to release, but setupActionMode now keeps action mode when --action-mode action is explicitly set. Update this field comment to match the current precedence rules so callers don’t rely on outdated semantics.

Suggested change
ActionTag string // Override action SHA or tag for actions/setup (overrides action-mode to release)
ActionTag string // Override action SHA or tag for actions/setup (forces release mode unless action-mode is explicitly set to 'action')

Copilot uses AI. Check for mistakes.
ActionsRepo string // Override the external actions repository (default: github/gh-aw-actions)
Stats bool // Display statistics table sorted by file size
FailFast bool // Stop at first error instead of collecting all errors
}
Expand Down
93 changes: 93 additions & 0 deletions pkg/cli/compile_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1980,3 +1980,96 @@ Call notify_dynamic to send notifications.
t.Errorf("Lock file should preserve the GitHub Actions expression in allowed_repositories\nLock file content:\n%s", lockContentStr)
}
}

// TestCompileWithActionsRepoFlag verifies that the --actions-repo flag causes the
// custom repository to be used in action mode instead of the default github/gh-aw-actions.
func TestCompileWithActionsRepoFlag(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

// Use the canonical test workflow fixture
srcPath := filepath.Join(projectRoot, "pkg/cli/workflows/test-actions-repo.md")
dstPath := filepath.Join(setup.workflowsDir, "test-actions-repo.md")

srcContent, err := os.ReadFile(srcPath)
if err != nil {
t.Fatalf("Failed to read source workflow file %s: %v", srcPath, err)
}
if err := os.WriteFile(dstPath, srcContent, 0644); err != nil {
t.Fatalf("Failed to write workflow to test dir: %v", err)
}

cmd := exec.Command(setup.binaryPath, "compile",
"--action-mode", "action",
"--actions-repo", "myorg/custom-aw-actions",
"--action-tag", "v9.9.9",
dstPath,
)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "test-actions-repo.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

// The custom repo should appear in the lock file
if !strings.Contains(lockContentStr, "myorg/custom-aw-actions") {
t.Errorf("Lock file should contain the custom actions repo 'myorg/custom-aw-actions'\nLock file content:\n%s", lockContentStr)
}

// The default repo should NOT appear
if strings.Contains(lockContentStr, "github/gh-aw-actions") {
t.Errorf("Lock file should NOT contain the default 'github/gh-aw-actions' when overridden\nLock file content:\n%s", lockContentStr)
}

t.Logf("Actions repo flag test passed - custom repo baked into lock file: %s", lockFilePath)
}

// TestCompileWithActionsRepoDefaultFallback verifies that without --actions-repo, the default
// github/gh-aw-actions repository is used in the lock file when action mode is set.
func TestCompileWithActionsRepoDefaultFallback(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()

// Use the canonical test workflow fixture
srcPath := filepath.Join(projectRoot, "pkg/cli/workflows/test-actions-repo.md")
dstPath := filepath.Join(setup.workflowsDir, "test-actions-repo.md")

srcContent, err := os.ReadFile(srcPath)
if err != nil {
t.Fatalf("Failed to read source workflow file %s: %v", srcPath, err)
}
if err := os.WriteFile(dstPath, srcContent, 0644); err != nil {
t.Fatalf("Failed to write workflow to test dir: %v", err)
}

// No --actions-repo flag; action mode with a tag
cmd := exec.Command(setup.binaryPath, "compile",
"--action-mode", "action",
"--action-tag", "v9.9.9",
dstPath,
)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("CLI compile command failed: %v\nOutput: %s", err, string(output))
}

lockFilePath := filepath.Join(setup.workflowsDir, "test-actions-repo.lock.yml")
lockContent, err := os.ReadFile(lockFilePath)
if err != nil {
t.Fatalf("Failed to read lock file: %v", err)
}
lockContentStr := string(lockContent)

// Default repo should appear in the lock file
if !strings.Contains(lockContentStr, "github/gh-aw-actions") {
t.Errorf("Lock file should contain the default 'github/gh-aw-actions' when no override is specified\nLock file content:\n%s", lockContentStr)
}

t.Logf("Default actions repo test passed - default repo baked into lock file: %s", lockFilePath)
}
16 changes: 16 additions & 0 deletions pkg/cli/workflows/test-actions-repo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
name: Test Actions Repo Override
on:
workflow_dispatch:
permissions:
contents: read
issues: read
engine: copilot
safe-outputs:
create-issue:
max: 1
---

# Test Actions Repo Override

When instructed, create an issue summarizing the repository state.
25 changes: 18 additions & 7 deletions pkg/workflow/action_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ const (
// - For action mode without resolver: "github/gh-aw-actions/setup@<version>" (tag-based, SHA resolved later)
// - Falls back to local path if version is invalid in release/action mode
func ResolveSetupActionReference(actionMode ActionMode, version string, actionTag string, resolver ActionSHAResolver) string {
return resolveSetupActionRef(actionMode, version, actionTag, resolver, "")
}

// resolveSetupActionRef is the internal implementation of ResolveSetupActionReference
// that accepts an optional actionsOrgRepo override. When actionsOrgRepo is empty,
// GitHubActionsOrgRepo is used.
func resolveSetupActionRef(actionMode ActionMode, version string, actionTag string, resolver ActionSHAResolver, actionsOrgRepo string) string {
if actionsOrgRepo == "" {
actionsOrgRepo = GitHubActionsOrgRepo
}

localPath := "./actions/setup"

// Dev mode - return local path
Expand All @@ -57,8 +68,8 @@ func ResolveSetupActionReference(actionMode ActionMode, version string, actionTa
return localPath
}

// Construct the remote reference: github/gh-aw-actions/setup@tag
actionRepo := GitHubActionsOrgRepo + "/setup"
// Construct the remote reference: <actionsOrgRepo>/setup@tag
actionRepo := actionsOrgRepo + "/setup"
remoteRef := fmt.Sprintf("%s@%s", actionRepo, tag)

// If a resolver is available, try to resolve the SHA
Expand Down Expand Up @@ -152,13 +163,13 @@ func (c *Compiler) resolveActionReference(localActionPath string, data *Workflow
resolver = data.ActionResolver
}
if c.actionTag != "" {
return ResolveSetupActionReference(c.actionMode, c.version, c.actionTag, resolver)
return resolveSetupActionRef(c.actionMode, c.version, c.actionTag, resolver, c.effectiveActionsRepo())
}
if !hasActionTag {
return ResolveSetupActionReference(c.actionMode, c.version, "", resolver)
return resolveSetupActionRef(c.actionMode, c.version, "", resolver, c.effectiveActionsRepo())
}
// hasActionTag is true and no compiler actionTag: use action mode with the frontmatter tag
return ResolveSetupActionReference(ActionModeAction, c.version, frontmatterActionTag, resolver)
return resolveSetupActionRef(ActionModeAction, c.version, frontmatterActionTag, resolver, c.effectiveActionsRepo())
}

// Action mode - use external gh-aw-actions repository
Expand Down Expand Up @@ -292,8 +303,8 @@ func (c *Compiler) convertToExternalActionsRef(localPath string, data *WorkflowD
}
}

// Construct the external actions reference: github/gh-aw-actions/action-name@tag
actionRepo := fmt.Sprintf("%s/%s", GitHubActionsOrgRepo, actionName)
// Construct the external actions reference: <actionsRepo>/action-name@tag
actionRepo := fmt.Sprintf("%s/%s", c.effectiveActionsRepo(), actionName)
remoteRef := fmt.Sprintf("%s@%s", actionRepo, tag)

// Try to resolve the SHA using action pins
Expand Down
21 changes: 21 additions & 0 deletions pkg/workflow/compiler_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ type Compiler struct {
markdownPath string // Path to the markdown file being compiled (for context in dynamic tool generation)
actionMode ActionMode // Mode for generating JavaScript steps (inline vs custom actions)
actionTag string // Override action SHA or tag for actions/setup (when set, overrides actionMode to release)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

Compiler.actionTag is commented as overriding actionMode to release, but setupActionMode now preserves ActionModeAction when explicitly requested alongside an action tag. Please update this struct field comment to reflect the current precedence (action-tag pins the version; it only forces release when action-mode isn’t explicitly set to action).

Suggested change
actionTag string // Override action SHA or tag for actions/setup (when set, overrides actionMode to release)
actionTag string // Override action SHA or tag for actions/setup; pins the version and only forces release when actionMode is not explicitly set to action

Copilot uses AI. Check for mistakes.
actionsRepo string // Override the external actions repository (default: github/gh-aw-actions)
jobManager *JobManager // Manages jobs and dependencies
engineRegistry *EngineRegistry // Registry of available agentic engines
engineCatalog *EngineCatalog // Catalog of engine definitions backed by the registry
Expand Down Expand Up @@ -216,6 +217,26 @@ func (c *Compiler) GetActionTag() string {
return c.actionTag
}

// SetActionsRepo sets the external actions repository override.
// When set, this overrides the default "github/gh-aw-actions" repository used in action mode.
func (c *Compiler) SetActionsRepo(repo string) {
c.actionsRepo = repo
}

// GetActionsRepo returns the external actions repository override (empty if not set)
func (c *Compiler) GetActionsRepo() string {
return c.actionsRepo
}

// effectiveActionsRepo returns the actions repository to use for action mode references.
// Returns the override if set, otherwise returns the default GitHubActionsOrgRepo constant.
func (c *Compiler) effectiveActionsRepo() string {
if c.actionsRepo != "" {
return c.actionsRepo
}
return GitHubActionsOrgRepo
}

// GetVersion returns the version string used by the compiler
func (c *Compiler) GetVersion() string {
return c.version
Expand Down
Loading