Skip to content

Commit bef7c14

Browse files
authored
fix: SHA-pin actions/setup in agentics-maintenance.yml generation (#18378)
1 parent caf3a0a commit bef7c14

7 files changed

Lines changed: 76 additions & 41 deletions

pkg/cli/pr_command_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,4 @@ func TestNewPRTransferSubcommand(t *testing.T) {
180180
if repoFlag == nil {
181181
t.Error("Expected --repo flag to exist")
182182
}
183-
184-
// Check that --verbose flag exists
185-
verboseFlag := cmd.Flags().Lookup("verbose")
186-
if verboseFlag == nil {
187-
t.Error("Expected --verbose flag to exist")
188-
}
189183
}

pkg/workflow/action_pins_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,9 +297,9 @@ func TestApplyActionPinToStep(t *testing.T) {
297297
func TestGetActionPinsSorting(t *testing.T) {
298298
pins := getActionPins()
299299

300-
// Verify we got all the pins (37 as of February 2026)
301-
if len(pins) != 37 {
302-
t.Errorf("getActionPins() returned %d pins, expected 37", len(pins))
300+
// Verify we got all the pins (39 as of February 2026)
301+
if len(pins) != 39 {
302+
t.Errorf("getActionPins() returned %d pins, expected 39", len(pins))
303303
}
304304

305305
// Verify they are sorted by version (descending) then by repository name (ascending)

pkg/workflow/action_reference.go

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ const (
2222
// - actionMode: The action mode (dev or release)
2323
// - version: The version string to use for release mode
2424
// - actionTag: Optional override tag/SHA (takes precedence over version when in release mode)
25-
// - data: Optional WorkflowData for SHA resolution (can be nil for standalone use)
25+
// - resolver: Optional ActionSHAResolver for dynamic SHA resolution (can be nil for standalone use)
2626
//
2727
// Returns:
2828
// - For dev mode: "./actions/setup" (local path)
29-
// - For release mode with data: "github/gh-aw/actions/setup@<sha> # <version>" (SHA-pinned)
30-
// - For release mode without data: "github/gh-aw/actions/setup@<version>" (tag-based, SHA resolved later)
29+
// - For release mode with resolver: "github/gh-aw/actions/setup@<sha> # <version>" (SHA-pinned)
30+
// - For release mode without resolver: "github/gh-aw/actions/setup@<version>" (tag-based, SHA resolved later)
3131
// - Falls back to local path if version is invalid in release mode
32-
func ResolveSetupActionReference(actionMode ActionMode, version string, actionTag string, data *WorkflowData) string {
32+
func ResolveSetupActionReference(actionMode ActionMode, version string, actionTag string, resolver ActionSHAResolver) string {
3333
localPath := "./actions/setup"
3434

3535
// Dev mode - return local path
@@ -55,25 +55,23 @@ func ResolveSetupActionReference(actionMode ActionMode, version string, actionTa
5555
}
5656

5757
// Construct the remote reference with tag: github/gh-aw/actions/setup@tag
58-
remoteRef := fmt.Sprintf("%s/%s@%s", GitHubOrgRepo, actionPath, tag)
59-
60-
// If WorkflowData is available, try to resolve the SHA
61-
if data != nil {
62-
actionRepo := fmt.Sprintf("%s/%s", GitHubOrgRepo, actionPath)
63-
pinnedRef, err := GetActionPinWithData(actionRepo, tag, data)
64-
if err != nil {
65-
// In strict mode, GetActionPinWithData returns an error
66-
actionRefLog.Printf("Failed to pin action %s@%s: %v", actionRepo, tag, err)
67-
return ""
68-
}
69-
if pinnedRef != "" {
70-
// Successfully resolved to SHA
58+
actionRepo := fmt.Sprintf("%s/%s", GitHubOrgRepo, actionPath)
59+
remoteRef := fmt.Sprintf("%s@%s", actionRepo, tag)
60+
61+
// If a resolver is available, try to resolve the SHA
62+
if resolver != nil {
63+
sha, err := resolver.ResolveSHA(actionRepo, tag)
64+
if err == nil && sha != "" {
65+
pinnedRef := formatActionReference(actionRepo, sha, tag)
7166
actionRefLog.Printf("Release mode: resolved %s to SHA-pinned reference: %s", remoteRef, pinnedRef)
7267
return pinnedRef
7368
}
69+
if err != nil {
70+
actionRefLog.Printf("Failed to resolve SHA for %s@%s: %v", actionRepo, tag, err)
71+
}
7472
}
7573

76-
// If WorkflowData is not available or SHA resolution failed, return tag-based reference
74+
// If no resolver or SHA resolution failed, return tag-based reference
7775
// This is for backward compatibility with standalone workflow generators
7876
actionRefLog.Printf("Release mode: using tag-based remote action reference: %s (SHA will be resolved later)", remoteRef)
7977
return remoteRef
@@ -104,11 +102,15 @@ func (c *Compiler) resolveActionReference(localActionPath string, data *Workflow
104102
// For ./actions/setup, check for compiler-level actionTag override first
105103
if localActionPath == "./actions/setup" {
106104
// Use compiler actionTag if available, otherwise check features
105+
var resolver ActionSHAResolver
106+
if data != nil && data.ActionResolver != nil {
107+
resolver = data.ActionResolver
108+
}
107109
if c.actionTag != "" {
108-
return ResolveSetupActionReference(c.actionMode, c.version, c.actionTag, data)
110+
return ResolveSetupActionReference(c.actionMode, c.version, c.actionTag, resolver)
109111
}
110112
if !hasActionTag {
111-
return ResolveSetupActionReference(c.actionMode, c.version, "", data)
113+
return ResolveSetupActionReference(c.actionMode, c.version, "", resolver)
112114
}
113115
}
114116

pkg/workflow/action_reference_test.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -341,20 +341,14 @@ func TestResolveSetupActionReference(t *testing.T) {
341341
}
342342

343343
func TestResolveSetupActionReferenceWithData(t *testing.T) {
344-
t.Run("release mode with WorkflowData resolves SHA", func(t *testing.T) {
344+
t.Run("release mode with resolver resolves SHA", func(t *testing.T) {
345345
// Create mock action resolver and cache
346346
cache := NewActionCache("")
347347
resolver := NewActionResolver(cache)
348348

349-
data := &WorkflowData{
350-
ActionResolver: resolver,
351-
ActionCache: cache,
352-
StrictMode: false,
353-
}
354-
355349
// The resolver will fail to resolve github/gh-aw/actions/setup@v1.0.0
356350
// since it's not a real tag, but it should fall back gracefully
357-
ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", data)
351+
ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", resolver)
358352

359353
// Without a valid pin or successful resolution, should return tag-based reference
360354
expectedRef := "github/gh-aw/actions/setup@v1.0.0"
@@ -363,7 +357,7 @@ func TestResolveSetupActionReferenceWithData(t *testing.T) {
363357
}
364358
})
365359

366-
t.Run("release mode with nil data returns tag-based reference", func(t *testing.T) {
360+
t.Run("release mode with nil resolver returns tag-based reference", func(t *testing.T) {
367361
ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", nil)
368362
expectedRef := "github/gh-aw/actions/setup@v1.0.0"
369363
if ref != expectedRef {

pkg/workflow/action_resolver.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111

1212
var resolverLog = logger.New("workflow:action_resolver")
1313

14+
// ActionSHAResolver is the minimal interface for resolving an action tag to its commit SHA.
15+
type ActionSHAResolver interface {
16+
ResolveSHA(repo, version string) (string, error)
17+
}
18+
1419
// ActionResolver handles resolving action SHAs using GitHub CLI
1520
type ActionResolver struct {
1621
cache *ActionCache

pkg/workflow/maintenance_workflow.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,12 @@ jobs:
149149
`)
150150

151151
// Get the setup action reference (local or remote based on mode)
152-
// Pass nil for data since maintenance workflow doesn't have WorkflowData
153-
// In release mode without data, it will return tag-based reference
154-
setupActionRef := ResolveSetupActionReference(actionMode, version, actionTag, nil)
152+
// Use the first available WorkflowData's ActionResolver to enable SHA pinning
153+
var resolver ActionSHAResolver
154+
if len(workflowDataList) > 0 && workflowDataList[0].ActionResolver != nil {
155+
resolver = workflowDataList[0].ActionResolver
156+
}
157+
setupActionRef := ResolveSetupActionReference(actionMode, version, actionTag, resolver)
155158

156159
// Add checkout step only in dev mode (for local action paths)
157160
if actionMode == ActionModeDev {

pkg/workflow/maintenance_workflow_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,43 @@ func TestGenerateMaintenanceWorkflow_ActionTag(t *testing.T) {
287287
}
288288
})
289289

290+
t.Run("release mode with action-tag and resolver uses SHA-pinned ref", func(t *testing.T) {
291+
tmpDir := t.TempDir()
292+
// Set up an action resolver with a cached SHA for the setup action
293+
cache := NewActionCache(tmpDir)
294+
cache.Set("github/gh-aw/actions/setup", "v0.47.4", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
295+
resolver := NewActionResolver(cache)
296+
297+
workflowDataListWithResolver := []*WorkflowData{
298+
{
299+
Name: "test-workflow",
300+
ActionResolver: resolver,
301+
ActionPinWarnings: make(map[string]bool),
302+
SafeOutputs: &SafeOutputsConfig{
303+
CreateIssues: &CreateIssuesConfig{
304+
Expires: 48,
305+
},
306+
},
307+
},
308+
}
309+
310+
err := GenerateMaintenanceWorkflow(workflowDataListWithResolver, tmpDir, "v1.0.0", ActionModeRelease, "v0.47.4", false)
311+
if err != nil {
312+
t.Fatalf("Unexpected error: %v", err)
313+
}
314+
content, err := os.ReadFile(filepath.Join(tmpDir, "agentics-maintenance.yml"))
315+
if err != nil {
316+
t.Fatalf("Expected maintenance workflow to be generated: %v", err)
317+
}
318+
expectedRef := "github/gh-aw/actions/setup@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # v0.47.4"
319+
if !strings.Contains(string(content), expectedRef) {
320+
t.Errorf("Expected SHA-pinned ref %q, got:\n%s", expectedRef, string(content))
321+
}
322+
if strings.Contains(string(content), "uses: ./actions/setup") {
323+
t.Errorf("Expected no local path in release mode with action-tag, got:\n%s", string(content))
324+
}
325+
})
326+
290327
t.Run("dev mode ignores action-tag and uses local path", func(t *testing.T) {
291328
tmpDir := t.TempDir()
292329
err := GenerateMaintenanceWorkflow(workflowDataList, tmpDir, "v1.0.0", ActionModeDev, "v0.47.4", false)

0 commit comments

Comments
 (0)