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
5 changes: 5 additions & 0 deletions .github/aw/actions-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@
"version": "v4.32.6",
"sha": "fb0994ef1c058010acf1efccff928b0a83b1ed54"
},
"github/gh-aw-actions/setup@v0": {
"repo": "github/gh-aw-actions/setup",
"version": "v0",
"sha": "c303e453d96fe6789ee8cb3d63033c710eac347a"
},
"github/stale-repos@v9.0.2": {
"repo": "github/stale-repos",
"version": "v9.0.2",
Expand Down
81 changes: 75 additions & 6 deletions .github/workflows/daily-fact.lock.yml

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

3 changes: 2 additions & 1 deletion .github/workflows/daily-fact.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ engine:
model: gpt-5.1-codex-mini
strict: true
timeout-minutes: 15
inlined-imports: true
features:
action-tag: "a70c5eada06553e3510ac27f2c3bda9d3705bccb"
action-tag: "v0"

network:
allowed:
Expand Down
31 changes: 14 additions & 17 deletions pkg/workflow/action_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,18 +125,21 @@ func ResolveSetupActionReference(actionMode ActionMode, version string, actionTa

// resolveActionReference converts a local action path to the appropriate reference
// based on the current action mode (dev vs release vs action).
// If action-tag is specified in features, it overrides the mode check and enables release mode behavior.
// If action-tag is specified in features, it overrides the mode check and enables action mode behavior
// (using the github/gh-aw-actions external repository).
// For dev mode: returns the local path as-is (e.g., "./actions/create-issue")
// For release mode: converts to SHA-pinned remote reference (e.g., "github/gh-aw/actions/create-issue@SHA # tag")
// For action mode: converts to SHA-pinned reference in external repo if possible (e.g., "github/gh-aw-actions/create-issue@SHA # version")
func (c *Compiler) resolveActionReference(localActionPath string, data *WorkflowData) string {
// Check if action-tag is specified in features - if so, override mode and use release behavior
// Check if action-tag is specified in features - if so, override mode and use action mode behavior
hasActionTag := false
var frontmatterActionTag string
if data != nil && data.Features != nil {
if actionTagVal, exists := data.Features["action-tag"]; exists {
if actionTagStr, ok := actionTagVal.(string); ok && actionTagStr != "" {
hasActionTag = true
actionRefLog.Printf("action-tag feature detected: %s - using release mode behavior", actionTagStr)
frontmatterActionTag = actionTagStr
actionRefLog.Printf("action-tag feature detected: %s - using action mode behavior", actionTagStr)
}
}
}
Expand All @@ -154,15 +157,17 @@ func (c *Compiler) resolveActionReference(localActionPath string, data *Workflow
if !hasActionTag {
return ResolveSetupActionReference(c.actionMode, c.version, "", resolver)
}
// hasActionTag is true and no compiler actionTag: use action mode with the frontmatter tag
return ResolveSetupActionReference(ActionModeAction, c.version, frontmatterActionTag, resolver)
}

// Action mode - use external gh-aw-actions repository with version tag (no SHA pinning)
if c.actionMode == ActionModeAction && !hasActionTag {
// Action mode - use external gh-aw-actions repository
if c.actionMode == ActionModeAction || hasActionTag {
return c.convertToExternalActionsRef(localActionPath, data)
}

// Use release mode if either actionMode is release OR action-tag is specified
if c.actionMode == ActionModeRelease || hasActionTag {
// Use release mode
if c.actionMode == ActionModeRelease {
// Convert to tag-based remote reference for release
remoteRef := c.convertToRemoteActionRef(localActionPath, data)
if remoteRef == "" {
Expand All @@ -185,22 +190,14 @@ func (c *Compiler) resolveActionReference(localActionPath string, data *Workflow
}
if pinnedRef != "" {
// Successfully resolved to SHA
if hasActionTag {
actionRefLog.Printf("action-tag override: resolved %s to SHA-pinned reference: %s", remoteRef, pinnedRef)
} else {
actionRefLog.Printf("Release mode: resolved %s to SHA-pinned reference: %s", remoteRef, pinnedRef)
}
actionRefLog.Printf("Release mode: resolved %s to SHA-pinned reference: %s", remoteRef, pinnedRef)
return pinnedRef
}
}

// If we couldn't resolve to SHA, return the tag-based reference
// This happens in non-strict mode when no pin is available
if hasActionTag {
actionRefLog.Printf("action-tag override: using tag-based remote action reference: %s", remoteRef)
} else {
actionRefLog.Printf("Release mode: using tag-based remote action reference: %s", remoteRef)
}
actionRefLog.Printf("Release mode: using tag-based remote action reference: %s", remoteRef)
return remoteRef
}

Expand Down
14 changes: 7 additions & 7 deletions pkg/workflow/action_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,26 +152,26 @@ func TestResolveActionReference(t *testing.T) {
localPath: "./actions/setup",
version: "v1.0.0",
actionTag: "latest",
expectedRef: "github/gh-aw/actions/setup@latest",
description: "Release mode with action-tag should use action-tag instead of version",
expectedRef: "github/gh-aw-actions/setup@latest",
description: "Frontmatter action-tag should use action mode (gh-aw-actions) regardless of compiler mode",
Comment on lines 152 to +156
},
{
name: "release mode with action-tag using SHA",
actionMode: ActionModeRelease,
localPath: "./actions/setup",
version: "v1.0.0",
actionTag: "abc123def456789",
expectedRef: "github/gh-aw/actions/setup@abc123def456789",
description: "Release mode with action-tag SHA should use the SHA",
expectedRef: "github/gh-aw-actions/setup@abc123def456789",
description: "Frontmatter action-tag SHA should use action mode (gh-aw-actions)",
},
{
name: "dev mode with action-tag uses remote reference",
name: "dev mode with action-tag uses external actions repo",
actionMode: ActionModeDev,
localPath: "./actions/setup",
version: "v1.0.0",
actionTag: "latest",
expectedRef: "github/gh-aw/actions/setup@latest",
description: "Dev mode with action-tag should override and use remote reference",
expectedRef: "github/gh-aw-actions/setup@latest",
description: "Dev mode with frontmatter action-tag should use action mode (gh-aw-actions)",
},
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/workflow/data/action_pins.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@
"version": "v4.32.6",
"sha": "fb0994ef1c058010acf1efccff928b0a83b1ed54"
},
"github/gh-aw-actions/setup@v0": {
"repo": "github/gh-aw-actions/setup",
"version": "v0",
"sha": "c303e453d96fe6789ee8cb3d63033c710eac347a"
},
"github/stale-repos@v9.0.2": {
"repo": "github/stale-repos",
"version": "v9.0.2",
Expand Down
32 changes: 19 additions & 13 deletions pkg/workflow/features_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
//
// This file validates feature flag values to ensure they meet requirements
// before being used in workflow compilation. It ensures that:
// - action-tag uses full 40-character SHA when specified
// - action-tag uses a full 40-character SHA or a version tag when specified
// - Other feature-specific constraints are met
//
// # Validation Functions
//
// - validateFeatures() - Validates all feature flags in WorkflowData
// - validateActionTag() - Validates action-tag is a full SHA
// - validateActionTag() - Validates action-tag is a full SHA or version tag
// - isValidFullSHA() - Checks if a string is a valid 40-character SHA
// - isValidVersionTag() - Checks if a string is a valid version tag (in semver.go)
//
// # When to Add Validation Here
//
Expand Down Expand Up @@ -54,7 +55,7 @@ func validateFeatures(data *WorkflowData) error {
return nil
}

// validateActionTag validates that action-tag is a full 40-character SHA when specified
// validateActionTag validates that action-tag is a full 40-character SHA or a version tag when specified
func validateActionTag(value any) error {
// Allow empty or nil values
if value == nil {
Expand All @@ -68,7 +69,7 @@ func validateActionTag(value any) error {
"features.action-tag",
fmt.Sprintf("%T", value),
fmt.Sprintf("action-tag must be a string, got %T", value),
"Provide a string value for action-tag. Example:\nfeatures:\n action-tag: \"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0\"",
"Provide a string value for action-tag. Example:\nfeatures:\n action-tag: \"v0\"",
)
}

Expand All @@ -77,17 +78,22 @@ func validateActionTag(value any) error {
return nil
}

// Validate it's a full SHA (40 hex characters)
if !isValidFullSHA(strVal) {
return NewValidationError(
"features.action-tag",
strVal,
fmt.Sprintf("action-tag must be a full 40-character commit SHA (length: %d). Short SHAs are not allowed", len(strVal)),
"Use 'git rev-parse <ref>' to get the full SHA. Example:\n\n$ git rev-parse HEAD\na1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0\n\nThen use in workflow:\nfeatures:\n action-tag: \"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0\"",
)
// Accept full 40-character commit SHA
if isValidFullSHA(strVal) {
return nil
}

return nil
// Accept version tags like "v0", "v1", "v1.0.0"
if isValidVersionTag(strVal) {
return nil
}

return NewValidationError(
"features.action-tag",
strVal,
fmt.Sprintf("action-tag must be a full 40-character commit SHA or a version tag (e.g. v0, v1.0.0). Got: %q", strVal),
"Use a version tag or a full commit SHA. Examples:\nfeatures:\n action-tag: \"v0\"\n\nOr with a full SHA:\nfeatures:\n action-tag: \"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0\"",
)
}

// isValidFullSHA checks if a string is a valid 40-character hexadecimal SHA
Expand Down
29 changes: 16 additions & 13 deletions pkg/workflow/features_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,23 +92,27 @@ func TestValidateActionTag(t *testing.T) {
value: nil,
expectError: false,
},
{
name: "valid - version tag v0",
value: "v0",
expectError: false,
},
{
name: "valid - version tag v1.0.0",
value: "v1.0.0",
expectError: false,
},
{
name: "invalid - short SHA (7 chars)",
value: "5c3428a",
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
errorMsg: "action-tag must be a full 40-character commit SHA or a version tag",
},
{
name: "invalid - short SHA (8 chars)",
value: "abc123de",
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
},
{
name: "invalid - version tag instead of SHA",
value: "v1.0.0",
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
errorMsg: "action-tag must be a full 40-character commit SHA or a version tag",
},
{
name: "invalid - not a string",
Expand All @@ -126,7 +130,7 @@ func TestValidateActionTag(t *testing.T) {
name: "invalid - uppercase SHA",
value: "ABCDEF0123456789ABCDEF0123456789ABCDEF01",
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
errorMsg: "action-tag must be a full 40-character commit SHA or a version tag",
},
}

Expand Down Expand Up @@ -182,17 +186,16 @@ func TestValidateFeatures(t *testing.T) {
},
},
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
errorMsg: "action-tag must be a full 40-character commit SHA or a version tag",
},
{
name: "invalid action-tag - version tag",
name: "valid action-tag - version tag",
data: &WorkflowData{
Features: map[string]any{
"action-tag": "v2.0.0",
},
},
expectError: true,
errorMsg: "action-tag must be a full 40-character commit SHA",
expectError: false,
},
{
name: "empty action-tag is allowed",
Expand Down
Loading
Loading