From 05b2c9ead97650331c6177bfdd2465f51bb5e0b6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:03:10 +0000 Subject: [PATCH 1/2] Initial plan From 0ddd2bf162362469ed2b715c61653946a7d46450 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:14:12 +0000 Subject: [PATCH 2/2] Fix action pinning resolution to prevent version switching When an action is resolved (e.g., actions/github-script@v8) and the cache already contains an entry with the same SHA but different version (e.g., v8.0.0), use the more precise existing version to maintain consistency and prevent version comment flipping in lock files. Changes: - Added ActionCache.FindEntryBySHA() to find entries by repo+SHA - Modified GetActionPinWithData() to check for existing cache entries with same SHA and use the more precise version - Added test coverage for FindEntryBySHA method Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/action_cache.go | 12 ++++++++++ pkg/workflow/action_cache_test.go | 39 +++++++++++++++++++++++++++++++ pkg/workflow/action_pins.go | 11 ++++++++- 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pkg/workflow/action_cache.go b/pkg/workflow/action_cache.go index 6ebed0e0e1..8cc48beaa8 100644 --- a/pkg/workflow/action_cache.go +++ b/pkg/workflow/action_cache.go @@ -177,6 +177,18 @@ func (c *ActionCache) Get(repo, version string) (string, bool) { return entry.SHA, true } +// FindEntryBySHA finds a cache entry with the given repo and SHA +// Returns the entry and true if found, or empty entry and false if not found +func (c *ActionCache) FindEntryBySHA(repo, sha string) (ActionCacheEntry, bool) { + for key, entry := range c.Entries { + if entry.Repo == repo && entry.SHA == sha { + actionCacheLog.Printf("Found cache entry for %s with SHA %s: %s", repo, sha[:8], key) + return entry, true + } + } + return ActionCacheEntry{}, false +} + // Set stores a new cache entry func (c *ActionCache) Set(repo, version, sha string) { key := repo + "@" + version diff --git a/pkg/workflow/action_cache_test.go b/pkg/workflow/action_cache_test.go index 0adfe8669e..701aede1f8 100644 --- a/pkg/workflow/action_cache_test.go +++ b/pkg/workflow/action_cache_test.go @@ -504,3 +504,42 @@ func TestActionCacheDirtyFlag(t *testing.T) { t.Fatalf("Failed to save dirty cache after modification: %v", err) } } + +// TestActionCacheFindEntryBySHA tests finding cache entries by SHA +func TestActionCacheFindEntryBySHA(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + // Add entries with same SHA + cache.Set("actions/github-script", "v8", "ed597411d8f924073f98dfc5c65a23a2325f34cd") + cache.Set("actions/github-script", "v8.0.0", "ed597411d8f924073f98dfc5c65a23a2325f34cd") + + // Find entry by SHA + entry, found := cache.FindEntryBySHA("actions/github-script", "ed597411d8f924073f98dfc5c65a23a2325f34cd") + if !found { + t.Fatal("Expected to find entry by SHA") + } + + // Should find one of the entries (either v8 or v8.0.0) + if entry.Repo != "actions/github-script" { + t.Errorf("Expected repo 'actions/github-script', got '%s'", entry.Repo) + } + if entry.SHA != "ed597411d8f924073f98dfc5c65a23a2325f34cd" { + t.Errorf("Expected SHA to match") + } + if entry.Version != "v8" && entry.Version != "v8.0.0" { + t.Errorf("Expected version 'v8' or 'v8.0.0', got '%s'", entry.Version) + } + + // Test not found case + _, found = cache.FindEntryBySHA("actions/unknown", "unknown-sha") + if found { + t.Error("Expected not to find entry with unknown SHA") + } + + // Test different repo with same SHA + _, found = cache.FindEntryBySHA("actions/checkout", "ed597411d8f924073f98dfc5c65a23a2325f34cd") + if found { + t.Error("Expected not to find entry for different repo") + } +} diff --git a/pkg/workflow/action_pins.go b/pkg/workflow/action_pins.go index 647cc96609..8af55d1e8c 100644 --- a/pkg/workflow/action_pins.go +++ b/pkg/workflow/action_pins.go @@ -155,19 +155,28 @@ func GetActionPinWithData(actionRepo, version string, data *WorkflowData) (strin actionPinsLog.Printf("Dynamic resolution succeeded: %s@%s → %s", actionRepo, version, sha) // Check if there are other cache entries with the same SHA + // If found, use the existing version to maintain consistency + canonicalVersion := version if data.ActionCache != nil { actionPinsLog.Printf("Checking cache for other versions with same SHA %s", sha[:8]) for key, entry := range data.ActionCache.Entries { if entry.Repo == actionRepo && entry.SHA == sha && entry.Version != version { actionPinsLog.Printf("Found cache entry with same SHA: %s (version=%s) vs requested version=%s", key, entry.Version, version) + // Use the existing version to prevent version comment switching + // This ensures all workflows use the same canonical version for a given SHA + if isMorePreciseVersion(entry.Version, version) { + canonicalVersion = entry.Version + actionPinsLog.Printf("Using existing more precise version %s instead of requested %s", + entry.Version, version) + } } } } // Successfully resolved - cache will be saved at end of compilation actionPinsLog.Printf("Successfully resolved action pin (cache marked dirty, will save at end)") - result := actionRepo + "@" + sha + " # " + version + result := actionRepo + "@" + sha + " # " + canonicalVersion actionPinsLog.Printf("Returning pinned reference: %s", result) return result, nil }