diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index 4d01b08683..33522f5351 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -13,13 +13,15 @@ var resolverLog = logger.New("workflow:action_resolver") // ActionResolver handles resolving action SHAs using GitHub CLI type ActionResolver struct { - cache *ActionCache + cache *ActionCache + failedResolutions map[string]bool // tracks failed resolution attempts in current run (key: "repo@version") } // NewActionResolver creates a new action resolver func NewActionResolver(cache *ActionCache) *ActionResolver { return &ActionResolver{ - cache: cache, + cache: cache, + failedResolutions: make(map[string]bool), } } @@ -28,6 +30,15 @@ func NewActionResolver(cache *ActionCache) *ActionResolver { func (r *ActionResolver) ResolveSHA(repo, version string) (string, error) { resolverLog.Printf("Resolving SHA for action: %s@%s", repo, version) + // Create a cache key for tracking failed resolutions + cacheKey := formatActionCacheKey(repo, version) + + // Check if we've already failed to resolve this action in this run + if r.failedResolutions[cacheKey] { + resolverLog.Printf("Skipping resolution for %s@%s: already failed in this run", repo, version) + return "", fmt.Errorf("previously failed to resolve %s@%s in this compilation run", repo, version) + } + // Check cache first if sha, found := r.cache.Get(repo, version); found { resolverLog.Printf("Cache hit for %s@%s: %s", repo, version, sha) @@ -41,6 +52,9 @@ func (r *ActionResolver) ResolveSHA(repo, version string) (string, error) { sha, err := r.resolveFromGitHub(repo, version) if err != nil { resolverLog.Printf("Failed to resolve %s@%s: %v", repo, version, err) + // Mark this resolution as failed for this compilation run + r.failedResolutions[cacheKey] = true + resolverLog.Printf("Marked %s as failed, will not retry in this run", cacheKey) return "", err } diff --git a/pkg/workflow/action_resolver_test.go b/pkg/workflow/action_resolver_test.go index e6aab7463c..f78fdc484d 100644 --- a/pkg/workflow/action_resolver_test.go +++ b/pkg/workflow/action_resolver_test.go @@ -3,6 +3,7 @@ package workflow import ( + "strings" "testing" "github.com/github/gh-aw/pkg/testutil" @@ -65,5 +66,41 @@ func TestActionResolverCache(t *testing.T) { } } +func TestActionResolverFailedResolutionCache(t *testing.T) { + // Create a cache and resolver + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + resolver := NewActionResolver(cache) + + // Attempt to resolve a non-existent action + // This will fail since we don't have a valid GitHub API connection in tests + repo := "nonexistent/action" + version := "v999.999.999" + + // First attempt should try to resolve + _, err1 := resolver.ResolveSHA(repo, version) + if err1 == nil { + t.Error("Expected error for non-existent action on first attempt") + } + + // Verify the failed resolution was tracked + cacheKey := formatActionCacheKey(repo, version) + if !resolver.failedResolutions[cacheKey] { + t.Errorf("Expected failed resolution to be tracked for %s", cacheKey) + } + + // Second attempt should be skipped and return error immediately + _, err2 := resolver.ResolveSHA(repo, version) + if err2 == nil { + t.Error("Expected error for non-existent action on second attempt") + } + + // Verify the error message indicates it was skipped + expectedErrMsg := "previously failed to resolve" + if !strings.Contains(err2.Error(), expectedErrMsg) { + t.Errorf("Expected error message to contain %q, got: %v", expectedErrMsg, err2) + } +} + // Note: Testing the actual GitHub API resolution requires network access // and is tested in integration tests or with network-dependent test tags