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
18 changes: 16 additions & 2 deletions pkg/workflow/action_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand All @@ -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)
Expand All @@ -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
}

Expand Down
37 changes: 37 additions & 0 deletions pkg/workflow/action_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package workflow

import (
"strings"
"testing"

"github.com/github/gh-aw/pkg/testutil"
Expand Down Expand Up @@ -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
Loading