compile: clarify --action-mode/--action-tag/--gh-aw-ref relationship, enforce mutual exclusivity#39697
Conversation
… mutual exclusivity Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR improves the gh aw compile CLI UX by clarifying how --action-mode, --action-tag, and --gh-aw-ref interact, and by enforcing flag mutual exclusivity to prevent silent overwrites.
Changes:
- Enforce mutual exclusivity so
--gh-aw-refcannot be combined with--action-modeor--action-tag. - Refresh compile help text and config/doc comments to reflect the supported action modes and intended usage.
- Update validation error messaging to list only the user-facing action modes.
Show a summary per file
| File | Description |
|---|---|
pkg/cli/compile_validation.go |
Updates the invalid action-mode error message to match the intended, user-facing mode set. |
pkg/cli/compile_config.go |
Updates CompileConfig field comments to reflect current action-mode/action-tag semantics. |
pkg/cli/compile_compiler_setup.go |
Improves setupActionMode docs/log wording to clarify precedence and --gh-aw-ref interaction. |
cmd/gh-aw/main.go |
Expands compile help text, updates flag help strings, and adds Cobra mutual-exclusivity enforcement for --gh-aw-ref. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 4/4 changed files
- Comments generated: 4
| - Only processes workflows in the default .github/workflows directory | ||
|
|
||
| Action mode controls how gh-aw action scripts are referenced in compiled workflows. | ||
| Three flags govern this and only one should be used at a time: |
There was a problem hiding this comment.
Updated in d11d8c0: the compile Long help now states that only --gh-aw-ref is mutually exclusive and that --action-tag can be combined with --action-mode action.
| Auto-detected from the binary build type when not set. | ||
|
|
||
| --action-tag <sha-or-tag> | ||
| Pin to a specific SHA or version tag (e.g. v1, v1.2.3, abc123...). |
There was a problem hiding this comment.
Updated in d11d8c0: the --action-tag Long-help example now uses <full-sha> instead of a short SHA example.
| ShowAllErrors bool // Display all prioritized errors instead of the default top five | ||
| 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) | ||
| ActionMode string // Action script inlining mode: dev, release, or action. Auto-detected if empty. |
There was a problem hiding this comment.
Updated in d11d8c0: CompileConfig.ActionMode comment now says “How action scripts are referenced” to match CLI semantics.
| 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) | ||
| ActionMode string // Action script inlining mode: dev, release, or action. Auto-detected if empty. | ||
| ActionTag string // Pin action refs to this SHA or version tag (e.g. v1, abc123…). Sets release mode unless ActionMode is already "action". Mutually exclusive with GHAwRef at the CLI layer. |
There was a problem hiding this comment.
Updated in d11d8c0: CompileConfig.ActionTag example now uses <full-sha> instead of a short SHA example.
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #39697 does not have the 'implementation' label and has only 16 new lines of code in business logic directories (≤100 threshold). |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
✅ Test Quality Sentinel completed test quality analysis. No test files were added or modified in this PR. Test Quality Sentinel skipped. PR #39697 only modified production source files: cmd/gh-aw/main.go, pkg/cli/compile_compiler_setup.go, pkg/cli/compile_config.go, pkg/cli/compile_validation.go. |
There was a problem hiding this comment.
Two non-blocking observations. No correctness bugs or crashes found.
🔍 Review themes
script mode: hidden but still functional
validateActionModeConfig no longer lists script in the error message, and script is absent from all flag help and Long documentation. But ActionModeScript is still in IsValid(), so --action-mode script is accepted silently — no error, no indication it works. The PR description says this is intentional ("user-facing modes only"), but the intent isn't communicated in code. Either retire it from IsValid() or annotate ActionModeScript as internal/deprecated.
setupActionMode godoc preamble
The new godoc reads "Priority order when both actionTag and actionMode are provided", but the three bullets actually cover all cases (one set, other empty, neither set). The preamble makes bullet 2 ambiguous — --action-mode dev --action-tag v1 silently uses release, not dev, which contradicts what a reader of bullet 2 would expect.
🔎 Code quality review by PR Code Quality Reviewer
| mode := workflow.ActionMode(actionMode) | ||
| if !mode.IsValid() { | ||
| return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', 'script', or 'action'", actionMode) | ||
| return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', or 'action'", actionMode) |
There was a problem hiding this comment.
script mode is silently accepted by IsValid() but now absent from all help and error guidance — a user passing --action-mode script gets no validation error but nothing tells them the mode exists or is supported.
💡 Details
ActionModeScript is still included in IsValid() in pkg/workflow/action_mode.go:
func (m ActionMode) IsValid() bool {
return m == ActionModeDev || m == ActionModeRelease || m == ActionModeScript || m == ActionModeAction
}So --action-mode script passes validation silently. But this PR simultaneously removes script from:
- this error message
- the
--action-modeflag description - the Long help text
This creates a hidden, undocumented but functional mode with no discoverability path. Two clean resolutions:
- Retire it: remove
ActionModeScriptfromIsValid()so the mode is truly invalid at the CLI layer. - Keep it internal: add a
// Internal:or// Deprecated:godoc toActionModeScriptinaction_mode.go, and leave a comment invalidateActionModeConfigexplaining why it is intentionally excluded from the user-facing error message.
Without one of these, a future developer will find a working hidden mode with no rationale for why it exists.
There was a problem hiding this comment.
Addressed in d11d8c0: added an explicit comment in validateActionModeConfig documenting that script is intentionally internal and excluded from user-facing error guidance.
| // setupActionMode configures how gh-aw action scripts are referenced in compiled workflows. | ||
| // Priority order when both actionTag and actionMode are provided (after --gh-aw-ref resolution): | ||
| // - actionTag != "" → pin to that SHA/tag; use ActionModeRelease unless actionMode is explicitly "action" | ||
| // - actionMode != "" → honour the explicit mode |
There was a problem hiding this comment.
Godoc preamble "when both are provided" makes bullet 2 misleading — a reader may infer that --action-mode dev --action-tag v1 uses dev mode; the code silently uses release instead.
💡 Suggested fix
The preamble scopes all three bullets to "when both actionTag and actionMode are provided", but bullet 2 only fires when actionTag is empty. For example:
--action-mode dev --action-tag v1→ bullet 1 fires, code setsActionModeRelease,devis ignored entirely--action-mode dev(no tag) → bullet 2 fires,devis honoured
Drop the "when both are provided" qualifier and add the actionTag == "" constraint to bullet 2:
// Priority order:
// - actionTag != "" → pin to that SHA/tag; use ActionModeRelease unless actionMode is explicitly "action"
// - actionTag == "" && actionMode != "" → honour the explicit mode
// - neither set → auto-detect from the binary build type via DetectActionModeThere was a problem hiding this comment.
Updated in d11d8c0: the setupActionMode godoc preamble now removes the “when both are provided” phrasing and explicitly scopes bullet 2 to actionTag == "".
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /tdd, /diagnose, /grill-with-docs, and /zoom-out — requesting changes on two blocking items.
📋 Key Themes & Highlights
Blocking
-
Missing regression tests (
/tdd): TheMarkFlagsMutuallyExclusivecalls are the core bug fix, but there is no test that exercises them. A future refactor could silently revert the fix with no CI signal. The existing test patterns incmd/gh-aw/main_entry_test.goandpkg/cli/compile_compiler_setup_test.gomake this straightforward to add. -
Documentation contradiction (
/grill-with-docs): Line 253 says "only one should be used at a time" but the flag descriptions below (and the code) explicitly support combining--action-tagwith--action-mode action. Only--gh-aw-refis actually mutually exclusive.
Non-blocking
-
Misleading godoc priority order (
/diagnose): ThesetupActionModecomment implies sequential evaluation, but the earlyreturnin theactionTagbranch meansactionModeis never separately evaluated when a tag is set. -
scriptmode gap (/diagnose):IsValid()still acceptsscript, but the error message no longer lists it. The intent should be made explicit with a comment. -
"40-character commit SHA" (
/zoom-out): No length validation is enforced; the phrasing will mislead users who pass abbreviated SHAs.
Positive Highlights
- ✅ The
MarkFlagsMutuallyExclusivefix is exactly right — Cobra's error is far better than silent clobbering - ✅ The new
Longdescription section fills a real documentation gap; the per-flag breakdown with examples is clear - ✅ Log message change from "overriding" → "defaulting" is a meaningful observability improvement
- ✅
CompileConfigfield comments are now self-consistent with the behavior
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer
| compileCmd.MarkFlagsMutuallyExclusive("dir", "workflows-dir") | ||
| // --gh-aw-ref is a convenience alias for --action-mode release --action-tag <sha>; | ||
| // combining it with either of those flags leads to one silently overwriting the other. | ||
| compileCmd.MarkFlagsMutuallyExclusive("gh-aw-ref", "action-tag") |
There was a problem hiding this comment.
[/tdd] No regression test for the mutual exclusivity enforcement — the core behavior fix has zero test coverage.
💡 Suggested approach
A unit test in cmd/gh-aw/main_entry_test.go (pattern already used there: rootCmd.SetArgs(...) + Execute()) could cover both error cases:
func TestCompileGhAwRefMutuallyExclusive(t *testing.T) {
cases := []struct {
name string
args []string
}{
{"gh-aw-ref+action-tag", []string{"compile", "--gh-aw-ref", "main", "--action-tag", "v1.2.3"}},
{"gh-aw-ref+action-mode", []string{"compile", "--gh-aw-ref", "main", "--action-mode", "dev"}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
rootCmd.SetArgs(tc.args)
err := rootCmd.Execute()
if err == nil {
t.Fatalf("expected mutual-exclusivity error, got nil")
}
})
}
}Without this, a future refactor removing MarkFlagsMutuallyExclusive would produce no CI signal.
There was a problem hiding this comment.
Addressed in d11d8c0: added TestCompileGhAwRefMutuallyExclusiveFlags in cmd/gh-aw/main_help_text_test.go to cover both --gh-aw-ref conflict cases.
| - Only processes workflows in the default .github/workflows directory | ||
|
|
||
| Action mode controls how gh-aw action scripts are referenced in compiled workflows. | ||
| Three flags govern this and only one should be used at a time: |
There was a problem hiding this comment.
[/grill-with-docs] "Only one should be used at a time" contradicts the documented ability to combine --action-tag with --action-mode action. Users who read only this header will think the flags are mutually exclusive, then be confused when the flag help says otherwise.
💡 Suggested fix
Replace:
Three flags govern this and only one should be used at a time:
With something like:
Three flags govern this. --gh-aw-ref is mutually exclusive with the other two;
--action-tag and --action-mode may be combined (e.g. --action-mode action --action-tag v1.2.3):
This matches the actual MarkFlagsMutuallyExclusive enforcement and the per-flag descriptions below.
There was a problem hiding this comment.
Updated in d11d8c0: replaced the contradictory “only one should be used at a time” header with wording that matches the actual exclusivity rules.
| // setupActionMode configures how gh-aw action scripts are referenced in compiled workflows. | ||
| // Priority order when both actionTag and actionMode are provided (after --gh-aw-ref resolution): | ||
| // - actionTag != "" → pin to that SHA/tag; use ActionModeRelease unless actionMode is explicitly "action" | ||
| // - actionMode != "" → honour the explicit mode |
There was a problem hiding this comment.
[/diagnose] The priority-order comment implies sequential evaluation, but the early return on line 223 means the actionMode != "" branch is never reached when actionTag is set — so --action-mode is effectively ignored (except for the action special-case handled inside the tag block). A reader following the godoc might incorrectly conclude that --action-mode dev --action-tag v1.2.3 would honor dev mode.
💡 Suggested clarification
// setupActionMode configures how gh-aw action scripts are referenced in compiled workflows.
// Resolution order (first match wins; function returns early after the actionTag branch):
// - actionTag != "" → pin to that SHA/tag; use ActionModeRelease unless actionMode is
// explicitly "action". Returns without evaluating actionMode further.
// - actionMode != "" → honour the explicit mode (only reached when actionTag is empty)
// - neither set → auto-detect from the binary build type via DetectActionModeThe "returns without evaluating" note prevents future contributors from adding code after the return under the false belief it will fire for the tag-set case.
There was a problem hiding this comment.
Updated in d11d8c0: clarified setupActionMode priority wording so the actionTag branch and actionTag == "" condition for explicit actionMode are clear.
| mode := workflow.ActionMode(actionMode) | ||
| if !mode.IsValid() { | ||
| return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', 'script', or 'action'", actionMode) | ||
| return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', or 'action'", actionMode) |
There was a problem hiding this comment.
[/diagnose] ActionModeScript.IsValid() still returns true (see action_mode.go:36), so --action-mode script silently passes validation even though the error message no longer lists it as valid. Decide whether script should be accepted at the CLI layer:
💡 Options
Option A — keep it as an undocumented internal escape hatch (current state), but add a comment here to make the gap intentional:
// Note: ActionModeScript is intentionally excluded — it is an internal mode
// not meant to be set directly by end users via the CLI.
return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', or 'action'", actionMode)Option B — explicitly block it at the CLI layer so the error message and accepted set agree:
if mode == workflow.ActionModeScript || !mode.IsValid() {
return fmt.Errorf("invalid action mode '%s'. Must be 'dev', 'release', or 'action'", actionMode)
}Option A is lower-risk if internal automation relies on script being passable.
There was a problem hiding this comment.
Addressed in d11d8c0: documented in validateActionModeConfig that ActionModeScript is intentionally internal and omitted from user-facing CLI mode guidance.
| 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") | ||
| compileCmd.Flags().String("action-mode", "", "How gh-aw action scripts are referenced in compiled workflows: 'dev' uses local paths (for developing gh-aw itself), 'release' emits SHA-pinned remote refs from github/gh-aw, 'action' uses the github/gh-aw-actions repository. Auto-detected from the binary build type if not specified") | ||
| compileCmd.Flags().String("action-tag", "", "Pin compiled workflows to a specific version of gh-aw actions. Accepts a full 40-character commit SHA or a version tag (e.g. v1, v1.2.3). Sets --action-mode to 'release' unless --action-mode action is also specified. Cannot be combined with --gh-aw-ref; use --gh-aw-ref when you want to resolve a branch or tag name to its current SHA") |
There was a problem hiding this comment.
[/zoom-out] "full 40-character commit SHA" is overly precise — SHA-1 is 40 hex chars, but the codebase also handles abbreviated SHAs in other contexts and does not validate length here. The prior help text said "full SHA" without the character count, which is more future-proof.
💡 Suggested wording
Change Accepts a full 40-character commit SHA or a version tag to Accepts a full commit SHA or a version tag — no length constraint is enforced by the flag parser, so stating one as fact will mislead users who pass a short SHA.
There was a problem hiding this comment.
Updated in d11d8c0: --action-tag flag help now says “full commit SHA” (without hard-coding 40 characters).
|
@copilot review all comments and address the requested changes. Then summarize any remaining blockers or follow-up checks.
|
|
@copilot run pr-finisher skill |
… test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
--gh-aw-refwas silently overwriting--action-tagand--action-modewhen combined, and--action-modehelp text listed the removedinlinevalue while omitting the validactionmode.Enforcement
MarkFlagsMutuallyExclusive("gh-aw-ref", "action-tag")and("gh-aw-ref", "action-mode")— Cobra now errors instead of silently discarding the other flagsHelp text fixes
--action-mode: removedinline(removed mode), addedaction, added per-value descriptions (dev= local paths,release= SHA-pinned fromgithub/gh-aw,action=github/gh-aw-actions)--action-tag: clarifies it accepts SHA or version tag only — branch names are not resolved; use--gh-aw-reffor that--gh-aw-ref: spells out the equivalence (--action-mode release --action-tag <resolved-sha>) and notes it cannot be combined with the other twoCompile command
LongdescriptionAdded an "Action mode" section documenting all three flags, their valid inputs, and when to use each — the compile help previously said nothing about this.
Internal docs
CompileConfigfield comments corrected (removedinline)setupActionModegodoc documents the priority order:actionTag→ explicitactionMode→ auto-detectvalidateActionModeConfigerror message updated to list only user-facing modes (dev,release,action)