From c8c128e9a02882aadfb72a5710c896f1d1c30cce Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 21:58:47 +0000 Subject: [PATCH 1/9] Initial plan From c73a3d18a7aed7e12e6a4e455a00435c6f0e6efb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:09:06 +0000 Subject: [PATCH 2/9] initial plan (no code changes yet) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-formal-spec-verifier.lock.yml | 7 +++---- actions/setup-cli/install.sh | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/daily-formal-spec-verifier.lock.yml b/.github/workflows/daily-formal-spec-verifier.lock.yml index ab8d6589f70..94d35789ac6 100644 --- a/.github/workflows/daily-formal-spec-verifier.lock.yml +++ b/.github/workflows/daily-formal-spec-verifier.lock.yml @@ -755,7 +755,7 @@ jobs: mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_6e6ad9d38cb21e0b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_33b6fb5d453a483d_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -764,7 +764,7 @@ jobs: "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw/mcp-logs/safeoutputs:/tmp/gh-aw/mcp-logs/safeoutputs:rw"], "args": ["-w", "\${GITHUB_WORKSPACE}"], "entrypoint": "sh", - "entrypointArgs": ["-c", "exec node ${RUNNER_TEMP}/gh-aw/safeoutputs/safe_outputs_mcp_server.cjs"], + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], "env": { "DEBUG": "*", "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", @@ -776,7 +776,6 @@ jobs: "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", - "GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}", "GITHUB_TOKEN": "\${GITHUB_TOKEN}", "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", "RUNNER_TEMP": "\${RUNNER_TEMP}" @@ -802,7 +801,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_6e6ad9d38cb21e0b_EOF + GH_AW_MCP_CONFIG_33b6fb5d453a483d_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true diff --git a/actions/setup-cli/install.sh b/actions/setup-cli/install.sh index d5488a9a7e2..c7a5ed2ffed 100755 --- a/actions/setup-cli/install.sh +++ b/actions/setup-cli/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set +o histexpand -# Kept in sync with ../../install-gh-aw.sh — edit that file, then copy here. +# Kept in sync with actions/setup-cli/install.sh — edit this file, then copy to that path. # Script to download and install gh-aw binary for the current OS and architecture # Supports: Linux, macOS (Darwin), FreeBSD, Windows (Git Bash/MSYS/Cygwin) From 5d7a94f0dbfeee3159800b71b2e13c1d37b983d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:20:22 +0000 Subject: [PATCH 3/9] fix: prune orphaned entries from actions-lock.json during compile Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_pipeline.go | 5 ++ pkg/cli/compile_post_processing.go | 25 +++++++ pkg/workflow/action_cache.go | 24 ++++++ pkg/workflow/action_cache_test.go | 105 +++++++++++++++++++++++++++ pkg/workflow/action_resolver.go | 11 +++ pkg/workflow/action_resolver_test.go | 29 ++++++++ pkg/workflow/compiler_types.go | 8 ++ 7 files changed, 207 insertions(+) diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index c0ab76c688c..5d81e821b01 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -605,6 +605,11 @@ func runPostProcessingForDirectory( // Prune stale gh-aw-actions entries before saving pruneStaleActionCacheEntries(compiler, actionCache) + // Prune orphaned entries — entries for action versions no longer referenced + // by any workflow in the directory (e.g. old pins left after a version bump). + // Safe to call only after a full-directory compilation (all workflows compiled). + pruneOrphanedActionCacheEntries(compiler, actionCache) + // Save action cache (errors are logged but non-fatal) _ = saveActionCache(actionCache, config.Verbose) diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 38b6a249ded..91c6a4aaa51 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -298,3 +298,28 @@ func pruneStaleActionCacheEntries(compiler *workflow.Compiler, actionCache *work actionCache.PruneStaleGHAWEntries(version, compiler.EffectiveActionsRepo()) } + +// pruneOrphanedActionCacheEntries removes entries from the action cache that were +// not referenced during the current compilation run. This garbage-collects entries +// for action versions no longer used by any workflow in the target directory (e.g. +// old version pins left behind after bumping a `uses:` tag). +// +// This is only safe to call after a full-directory compilation — compiling a +// subset of files would incorrectly prune entries still referenced by other +// (uncompiled) workflows. +func pruneOrphanedActionCacheEntries(compiler *workflow.Compiler, actionCache *workflow.ActionCache) { + if actionCache == nil { + return + } + + resolver := compiler.GetSharedActionResolver() + if resolver == nil { + return + } + + usedKeys := resolver.GetUsedCacheKeys() + pruned := actionCache.PruneOrphanedEntries(usedKeys) + if pruned > 0 { + compilePostProcessingLog.Printf("Pruned %d orphaned entries from actions-lock.json", pruned) + } +} diff --git a/pkg/workflow/action_cache.go b/pkg/workflow/action_cache.go index 800edf44f98..7f57ce0a8d1 100644 --- a/pkg/workflow/action_cache.go +++ b/pkg/workflow/action_cache.go @@ -96,6 +96,30 @@ func (c *ActionCache) DeleteContainerPin(image string) { } } +// PruneOrphanedEntries removes action cache entries whose keys are not present +// in referencedKeys. It returns the number of entries that were removed. +// This is used to keep actions-lock.json a faithful reflection of what the +// compiled workflows actually reference — entries for old action versions that +// are no longer used by any workflow are removed. +func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { + if len(referencedKeys) == 0 { + return 0 + } + pruned := 0 + for key := range c.Entries { + if !referencedKeys[key] { + delete(c.Entries, key) + c.dirty = true + pruned++ + actionCacheLog.Printf("Pruned orphaned action cache entry: %s", key) + } + } + if pruned > 0 { + actionCacheLog.Printf("Pruned %d orphaned action cache entries, %d entries remaining", pruned, len(c.Entries)) + } + return pruned +} + // PruneStaleContainerPins removes container pin entries whose keys are not present // in knownImages. It returns the number of entries that were removed. // This is used to keep actions-lock.json consistent with the set of images diff --git a/pkg/workflow/action_cache_test.go b/pkg/workflow/action_cache_test.go index 9014f2aa58f..a1bc7941d79 100644 --- a/pkg/workflow/action_cache_test.go +++ b/pkg/workflow/action_cache_test.go @@ -881,3 +881,108 @@ func TestActionCacheReleasedAtClearedOnSHAChange(t *testing.T) { t.Error("ReleasedAt should be cleared when SHA changes") } } + +// TestPruneOrphanedEntries verifies that PruneOrphanedEntries removes entries +// whose keys are absent from the referenced set, while preserving referenced ones. +func TestPruneOrphanedEntries(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + // Simulate a version bump: v1.7.2 was old, v1.9.1 is now used. + cache.Set("microsoft/apm-action", "v1.7.2", "sha_old") + cache.Set("microsoft/apm-action", "v1.9.1", "sha_new") + cache.Set("actions/checkout", "v4", "sha_checkout") + + if len(cache.Entries) != 3 { + t.Fatalf("Expected 3 entries before pruning, got %d", len(cache.Entries)) + } + + // Only v1.9.1 and checkout are referenced by compiled workflows. + referenced := map[string]bool{ + "microsoft/apm-action@v1.9.1": true, + "actions/checkout@v4": true, + } + pruned := cache.PruneOrphanedEntries(referenced) + + if pruned != 1 { + t.Errorf("Expected 1 pruned entry, got %d", pruned) + } + if len(cache.Entries) != 2 { + t.Errorf("Expected 2 entries after pruning, got %d", len(cache.Entries)) + } + if _, exists := cache.Entries["microsoft/apm-action@v1.7.2"]; exists { + t.Error("Expected orphaned entry microsoft/apm-action@v1.7.2 to be removed") + } + if _, exists := cache.Entries["microsoft/apm-action@v1.9.1"]; !exists { + t.Error("Expected referenced entry microsoft/apm-action@v1.9.1 to remain") + } + if _, exists := cache.Entries["actions/checkout@v4"]; !exists { + t.Error("Expected referenced entry actions/checkout@v4 to remain") + } +} + +// TestPruneOrphanedEntries_EmptyReferenced verifies that passing an empty +// referenced set is a no-op (safe guard: an empty set most likely means no +// compilation happened, not that all entries are orphaned). +func TestPruneOrphanedEntries_EmptyReferenced(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + cache.Set("actions/checkout", "v4", "sha1") + cache.Set("actions/setup-node", "v4", "sha2") + + pruned := cache.PruneOrphanedEntries(map[string]bool{}) + + if pruned != 0 { + t.Errorf("Expected 0 pruned entries (empty referenced set is a no-op), got %d", pruned) + } + if len(cache.Entries) != 2 { + t.Errorf("Expected 2 entries (unchanged), got %d", len(cache.Entries)) + } +} + +// TestPruneOrphanedEntries_NoneOrphaned verifies that no entries are removed +// when all cached entries are referenced. +func TestPruneOrphanedEntries_NoneOrphaned(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + cache.Set("actions/checkout", "v4", "sha1") + cache.Set("actions/setup-node", "v4", "sha2") + + referenced := map[string]bool{ + "actions/checkout@v4": true, + "actions/setup-node@v4": true, + } + pruned := cache.PruneOrphanedEntries(referenced) + + if pruned != 0 { + t.Errorf("Expected 0 pruned entries, got %d", pruned) + } + if len(cache.Entries) != 2 { + t.Errorf("Expected 2 entries (unchanged), got %d", len(cache.Entries)) + } +} + +// TestPruneOrphanedEntries_AllOrphaned verifies that all entries are removed +// when none are referenced (but referenced set is non-empty). +func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + cache.Set("actions/checkout", "v4", "sha1") + cache.Set("actions/setup-node", "v4", "sha2") + + // Referenced set is non-empty but contains neither of the cached entries. + referenced := map[string]bool{ + "some/other-action@v1": true, + } + pruned := cache.PruneOrphanedEntries(referenced) + + if pruned != 2 { + t.Errorf("Expected 2 pruned entries, got %d", pruned) + } + if len(cache.Entries) != 0 { + t.Errorf("Expected 0 entries after pruning all orphans, got %d", len(cache.Entries)) + } +} diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index f586dda6011..bf190a69d7c 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -18,6 +18,7 @@ var resolverLog = logger.New("workflow:action_resolver") type ActionResolver struct { cache *ActionCache failedResolutions map[string]bool // tracks failed resolution attempts in current run (key: "repo@version") + usedCacheKeys map[string]bool // tracks cache keys that were hit or newly set during this run } // NewActionResolver creates a new action resolver @@ -25,9 +26,17 @@ func NewActionResolver(cache *ActionCache) *ActionResolver { return &ActionResolver{ cache: cache, failedResolutions: make(map[string]bool), + usedCacheKeys: make(map[string]bool), } } +// GetUsedCacheKeys returns the set of cache keys (in "repo@version" format) that +// were successfully resolved from the cache or written to the cache during this run. +// These represent the action pins actually referenced by the compiled workflows. +func (r *ActionResolver) GetUsedCacheKeys() map[string]bool { + return r.usedCacheKeys +} + // ResolveSHA resolves the SHA for a given action@version using GitHub CLI // Returns the SHA and an error if resolution fails func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) (string, error) { @@ -46,6 +55,7 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Check cache first using the pre-computed key to avoid a second key allocation. if sha, found := r.cache.GetByCacheKey(cacheKey); found { resolverLog.Printf("Cache hit for %s@%s: %s", repo, version, sha) + r.usedCacheKeys[cacheKey] = true return sha, nil } @@ -95,6 +105,7 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Cache the result resolverLog.Printf("Caching result: %s@%s → %s", repo, version, sha) r.cache.Set(repo, version, sha) + r.usedCacheKeys[cacheKey] = true return sha, nil } diff --git a/pkg/workflow/action_resolver_test.go b/pkg/workflow/action_resolver_test.go index e193b87cf9d..2deb394de52 100644 --- a/pkg/workflow/action_resolver_test.go +++ b/pkg/workflow/action_resolver_test.go @@ -202,3 +202,32 @@ func TestParseTagRefTSV(t *testing.T) { }) } } + +// TestActionResolverUsedCacheKeysOnCacheHit verifies that GetUsedCacheKeys tracks +// cache hits — i.e. keys that were already in the cache and returned by ResolveSHA. +func TestActionResolverUsedCacheKeysOnCacheHit(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + resolver := NewActionResolver(cache) + + // Pre-populate the cache with two entries. + cache.Set("owner/action-a", "v1", "sha_a") + cache.Set("owner/action-b", "v2", "sha_b") + + // Resolve only action-a — it should appear in UsedCacheKeys. + sha, err := resolver.ResolveSHA(context.Background(), "owner/action-a", "v1") + if err != nil { + t.Fatalf("Expected no error for cached entry, got: %v", err) + } + if sha != "sha_a" { + t.Errorf("Expected sha_a, got %q", sha) + } + + usedKeys := resolver.GetUsedCacheKeys() + if !usedKeys["owner/action-a@v1"] { + t.Error("Expected owner/action-a@v1 to be in used cache keys after a cache hit") + } + if usedKeys["owner/action-b@v2"] { + t.Error("Expected owner/action-b@v2 to be absent from used cache keys (never resolved)") + } +} diff --git a/pkg/workflow/compiler_types.go b/pkg/workflow/compiler_types.go index 852a1646b13..c4f1fa6c211 100644 --- a/pkg/workflow/compiler_types.go +++ b/pkg/workflow/compiler_types.go @@ -427,6 +427,14 @@ func (c *Compiler) GetSharedActionCache() *ActionCache { return cache } +// GetSharedActionResolver returns the shared action resolver used by this compiler instance. +// The resolver is lazily initialized on first access and shared across all workflows. +// It tracks which cache keys were used during compilation, enabling orphaned-entry pruning. +func (c *Compiler) GetSharedActionResolver() *ActionResolver { + _, resolver := c.getSharedActionResolver() + return resolver +} + // SkipIfMatchConfig holds the configuration for skip-if-match conditions type SkipIfMatchConfig struct { Query string // GitHub search query to check before running workflow From da93d81ef81682523a2775a41819c3d7b707bf61 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 22:35:21 +0000 Subject: [PATCH 4/9] Apply remaining changes Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/actions-lock.json | 90 ------------------------------------ 1 file changed, 90 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 0a684211ed0..da4c5673dd8 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -23,41 +23,16 @@ }, "action_description": "Add labels to an issue or a pull request." }, - "actions/ai-inference@v2.1.1": { - "repo": "actions/ai-inference", - "version": "v2.1.1", - "sha": "a7805884c80886efc241e94a5351df715968a0ad" - }, - "actions/attest-build-provenance@v4.1.0": { - "repo": "actions/attest-build-provenance", - "version": "v4.1.0", - "sha": "a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32" - }, "actions/cache/restore@v5.0.5": { "repo": "actions/cache/restore", "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, - "actions/cache/save@v5.0.5": { - "repo": "actions/cache/save", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, - "actions/cache@v5.0.5": { - "repo": "actions/cache", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, - "actions/create-github-app-token@v3.2.0": { - "repo": "actions/create-github-app-token", - "version": "v3.2.0", - "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" - }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -68,21 +43,11 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "actions/setup-dotnet@v5.3.0": { - "repo": "actions/setup-dotnet", - "version": "v5.3.0", - "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" - }, "actions/setup-go@v6.4.0": { "repo": "actions/setup-go", "version": "v6.4.0", "sha": "4a3601121dd01d1626a1e23e37211e3254c1c06c" }, - "actions/setup-java@v5.2.0": { - "repo": "actions/setup-java", - "version": "v5.2.0", - "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" - }, "actions/setup-node@v6.4.0": { "repo": "actions/setup-node", "version": "v6.4.0", @@ -103,21 +68,6 @@ "version": "v0.24.0", "sha": "e22c389904149dbc22b58101806040fa8d37a610" }, - "astral-sh/setup-uv@v8.2.0": { - "repo": "astral-sh/setup-uv", - "version": "v8.2.0", - "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" - }, - "cli/gh-extension-precompile@v2.1.0": { - "repo": "cli/gh-extension-precompile", - "version": "v2.1.0", - "sha": "9e2237c30f869ad3bcaed6a4be2cd43564dd421b" - }, - "denoland/setup-deno@v2.0.4": { - "repo": "denoland/setup-deno", - "version": "v2.0.4", - "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" - }, "docker/build-push-action@v7.2.0": { "repo": "docker/build-push-action", "version": "v7.2.0", @@ -140,51 +90,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, - "erlef/setup-beam@v1.24.0": { - "repo": "erlef/setup-beam", - "version": "v1.24.0", - "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" - }, - "github/codeql-action/upload-sarif@v4.36.2": { - "repo": "github/codeql-action/upload-sarif", - "version": "v4.36.2", - "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" - }, - "github/gh-aw-actions/setup@v0.79.8": { - "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" - }, - "github/gh-aw/actions/setup-cli@v0.79.8": { - "repo": "github/gh-aw/actions/setup-cli", - "version": "v0.79.8", - "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" - }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", "sha": "378f317724a25737b846bd9895bacf5726dd72ff" }, - "haskell-actions/setup@v2.11.0": { - "repo": "haskell-actions/setup", - "version": "v2.11.0", - "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" - }, - "microsoft/apm-action@v1.9.1": { - "repo": "microsoft/apm-action", - "version": "v1.9.1", - "sha": "e5650fb81c4b5965090a17bd1ed1956071e95d17" - }, - "oven-sh/setup-bun@v2.2.0": { - "repo": "oven-sh/setup-bun", - "version": "v2.2.0", - "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" - }, - "ruby/setup-ruby@v1.313.0": { - "repo": "ruby/setup-ruby", - "version": "v1.313.0", - "sha": "89f90524b88a01fe6e0b732220432cc6142926af" - }, "safedep/pmg@v1": { "repo": "safedep/pmg", "version": "v1", From 5667e11e754038617248c3198270ea24f3c549ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:29:44 +0000 Subject: [PATCH 5/9] chore: start PR feedback triage Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic_commands.yml | 134 --------------------- .github/workflows/agentics-maintenance.yml | 5 +- actions/setup-cli/install.sh | 2 +- pkg/actionpins/data/action_pins.json | 90 -------------- pkg/workflow/data/action_pins.json | 90 -------------- 5 files changed, 4 insertions(+), 317 deletions(-) delete mode 100644 .github/workflows/agentic_commands.yml diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml deleted file mode 100644 index e46cbfbcd3a..00000000000 --- a/.github/workflows/agentic_commands.yml +++ /dev/null @@ -1,134 +0,0 @@ -# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"dev","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","matt","mergefest","nit","plan","poem-bot","review","ruflo","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-antigravity","smoke-call-workflow","smoke-claude","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-sdk","smoke-create-cross-repo-pr","smoke-crush","smoke-gemini","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","ci-doctor","cloclo","craft","design-decision-gate","dev","grumpy-reviewer","mattpocock-skills-reviewer","mergefest","necromancer","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","ruflo-backed-task","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-antigravity","smoke-call-workflow","smoke-claude","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-sdk","smoke-create-cross-repo-pr","smoke-crush","smoke-gemini","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","test-quality-sentinel","tidy","unbloat-docs"]} -# Routing summary (sorted): -# slash commands: -# /ace -> ace-editor [pull_request_comment] reaction=eyes -# /approach-validator -> approach-validator [issue_comment,pull_request_comment] reaction=eyes -# /archie -> archie [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /brave -> brave [issue_comment] reaction=eyes -# /cloclo -> cloclo [discussion,discussion_comment,issue_comment,issues,pull_request,pull_request_comment,pull_request_review_comment] reaction=eyes -# /craft -> craft [issues] reaction=eyes -# /grumpy -> grumpy-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes -# /matt -> mattpocock-skills-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes -# /mergefest -> mergefest [pull_request_comment] reaction=eyes -# /nit -> pr-nitpick-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes -# /plan -> plan [discussion_comment,issue_comment] reaction=eyes -# /poem-bot -> poem-bot [issues] reaction=eyes -# /review -> design-decision-gate [pull_request_comment,pull_request_review_comment] reaction=eyes -# /review -> pr-code-quality-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes -# /review -> test-quality-sentinel [pull_request_comment,pull_request_review_comment] reaction=eyes -# /ruflo -> ruflo-backed-task [issue_comment] reaction=eyes -# /scout -> scout [discussion,discussion_comment,issue_comment,issues,pull_request,pull_request_comment,pull_request_review_comment] reaction=eyes -# /security-review -> security-review [pull_request_comment,pull_request_review_comment] reaction=eyes -# /smoke-agent-all-merged -> smoke-agent-all-merged [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-agent-all-none -> smoke-agent-all-none [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-agent-public-approved -> smoke-agent-public-approved [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-agent-public-none -> smoke-agent-public-none [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-agent-scoped-approved -> smoke-agent-scoped-approved [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-antigravity -> smoke-antigravity [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket -# /smoke-call-workflow -> smoke-call-workflow [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-claude -> smoke-claude [issue_comment,issues,pull_request,pull_request_comment] reaction=heart -# /smoke-codex -> smoke-codex [issue_comment,issues,pull_request,pull_request_comment] reaction=hooray -# /smoke-copilot -> smoke-copilot [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-copilot-aoai-apikey -> smoke-copilot-aoai-apikey [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-copilot-aoai-entra -> smoke-copilot-aoai-entra [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-copilot-arm -> smoke-copilot-arm [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-copilot-sdk -> smoke-copilot-sdk [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-create-cross-repo-pr -> smoke-create-cross-repo-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-crush -> smoke-crush [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-gemini -> smoke-gemini [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket -# /smoke-multi-pr -> smoke-multi-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-opencode -> smoke-opencode [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket -# /smoke-otel-backends -> smoke-otel-backends [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-pi -> smoke-pi [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket -# /smoke-project -> smoke-project [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-service-ports -> smoke-service-ports [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-temporary-id -> smoke-temporary-id [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-test-tools -> smoke-test-tools [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /smoke-update-cross-repo-pr -> smoke-update-cross-repo-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes -# /summarize -> pdf-summary [issue_comment,issues] reaction=eyes -# /tidy -> tidy [pull_request_comment] reaction=eyes -# /unbloat -> unbloat-docs [pull_request_comment] reaction=eyes -# labels: -# approach-proposal -> approach-validator [issues,pull_request] reaction=eyes -# ci-doctor -> ci-doctor [pull_request] reaction=eyes -# cloclo -> cloclo [discussion,issues,pull_request] reaction=eyes -# dev -> dev [discussion,issues,pull_request] reaction=eyes -# necromancer -> necromancer [pull_request] reaction=eyes -# needs-design -> approach-validator [issues,pull_request] reaction=eyes -# smoke -> smoke-copilot [pull_request] reaction=eyes -# smoke -> smoke-copilot-aoai-apikey [pull_request] reaction=eyes -# smoke -> smoke-copilot-aoai-entra [pull_request] reaction=eyes -# smoke -> smoke-otel-backends [pull_request] reaction=eyes -# smoke-sdk -> smoke-copilot-sdk [pull_request] reaction=eyes -# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md -# -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# -# To regenerate this workflow, run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -name: "Agentic Commands" - -on: - issues: - types: [edited, labeled, opened, reopened] - issue_comment: - types: [created, edited] - pull_request: - types: [edited, labeled, opened, reopened] - pull_request_review_comment: - types: [created, edited] - discussion: - types: [created, edited, labeled] - discussion_comment: - types: [created, edited] - -permissions: {} - -jobs: - route: - runs-on: ubuntu-slim - timeout-minutes: 15 - permissions: - actions: write - contents: read - issues: write - pull-requests: write - discussions: write - - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - - name: Setup Scripts - uses: ./actions/setup - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Route slash command - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_SLASH_ROUTING: '{"ace":[{"workflow":"ace-editor","events":["pull_request_comment"],"ai_reaction":"eyes"}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"],"ai_reaction":"eyes"}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"brave":[{"workflow":"brave","events":["issue_comment"],"ai_reaction":"eyes"}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"craft":[{"workflow":"craft","events":["issues"],"ai_reaction":"eyes"}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"matt":[{"workflow":"mattpocock-skills-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"],"ai_reaction":"eyes"}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"],"ai_reaction":"eyes"}],"poem-bot":[{"workflow":"poem-bot","events":["issues"],"ai_reaction":"eyes"}],"review":[{"workflow":"design-decision-gate","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"},{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"},{"workflow":"test-quality-sentinel","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"ruflo":[{"workflow":"ruflo-backed-task","events":["issue_comment"],"ai_reaction":"eyes"}],"scout":[{"workflow":"scout","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"smoke-agent-all-merged":[{"workflow":"smoke-agent-all-merged","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-all-none":[{"workflow":"smoke-agent-all-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-public-approved":[{"workflow":"smoke-agent-public-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-public-none":[{"workflow":"smoke-agent-public-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-scoped-approved":[{"workflow":"smoke-agent-scoped-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-antigravity":[{"workflow":"smoke-antigravity","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-call-workflow":[{"workflow":"smoke-call-workflow","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-claude":[{"workflow":"smoke-claude","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"heart"}],"smoke-codex":[{"workflow":"smoke-codex","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"hooray"}],"smoke-copilot":[{"workflow":"smoke-copilot","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-aoai-apikey":[{"workflow":"smoke-copilot-aoai-apikey","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-aoai-entra":[{"workflow":"smoke-copilot-aoai-entra","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-arm":[{"workflow":"smoke-copilot-arm","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-sdk":[{"workflow":"smoke-copilot-sdk","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-create-cross-repo-pr":[{"workflow":"smoke-create-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-crush":[{"workflow":"smoke-crush","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-gemini":[{"workflow":"smoke-gemini","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-multi-pr":[{"workflow":"smoke-multi-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-opencode":[{"workflow":"smoke-opencode","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-otel-backends":[{"workflow":"smoke-otel-backends","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-pi":[{"workflow":"smoke-pi","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-project":[{"workflow":"smoke-project","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-service-ports":[{"workflow":"smoke-service-ports","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-temporary-id":[{"workflow":"smoke-temporary-id","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-test-tools":[{"workflow":"smoke-test-tools","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-update-cross-repo-pr":[{"workflow":"smoke-update-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"],"ai_reaction":"eyes"}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"],"ai_reaction":"eyes"}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"],"ai_reaction":"eyes"}]}' - GH_AW_LABEL_ROUTING: '{"approach-proposal":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes"}],"ci-doctor":[{"workflow":"ci-doctor","events":["pull_request"],"ai_reaction":"eyes"}],"cloclo":[{"workflow":"cloclo","events":["discussion","issues","pull_request"],"ai_reaction":"eyes"}],"dev":[{"workflow":"dev","events":["discussion","issues","pull_request"],"ai_reaction":"eyes"}],"necromancer":[{"workflow":"necromancer","events":["pull_request"],"ai_reaction":"eyes"}],"needs-design":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes"}],"smoke":[{"workflow":"smoke-copilot","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-copilot-aoai-apikey","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-copilot-aoai-entra","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-otel-backends","events":["pull_request"],"ai_reaction":"eyes"}],"smoke-sdk":[{"workflow":"smoke-copilot-sdk","events":["pull_request"],"ai_reaction":"eyes"}]}' - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/route_slash_command.cjs'); - await main(); diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index 6dc884a6868..b71b17f3e21 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -431,7 +431,7 @@ jobs: - name: Save activity report logs cache if: ${{ always() }} - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: with: path: ./.cache/gh-aw/activity-report-logs key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} @@ -562,7 +562,7 @@ jobs: - name: Save forecast report logs cache if: ${{ always() }} - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + uses: with: path: ./.github/aw/logs key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} @@ -833,6 +833,7 @@ jobs: GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} GH_AW_COPILOT_TOKEN: ${{ secrets.GH_AW_COPILOT_TOKEN }} + GH_AW_COPILOT_ORG_BILLING: "true" # AI Engine API keys ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/actions/setup-cli/install.sh b/actions/setup-cli/install.sh index 5635319fd84..c7a5ed2ffed 100755 --- a/actions/setup-cli/install.sh +++ b/actions/setup-cli/install.sh @@ -1,7 +1,7 @@ #!/bin/bash set +o histexpand -# Kept in sync with install-gh-aw.sh — edit that file, then copy to this path. +# Kept in sync with actions/setup-cli/install.sh — edit this file, then copy to that path. # Script to download and install gh-aw binary for the current OS and architecture # Supports: Linux, macOS (Darwin), FreeBSD, Windows (Git Bash/MSYS/Cygwin) diff --git a/pkg/actionpins/data/action_pins.json b/pkg/actionpins/data/action_pins.json index 0a684211ed0..da4c5673dd8 100644 --- a/pkg/actionpins/data/action_pins.json +++ b/pkg/actionpins/data/action_pins.json @@ -23,41 +23,16 @@ }, "action_description": "Add labels to an issue or a pull request." }, - "actions/ai-inference@v2.1.1": { - "repo": "actions/ai-inference", - "version": "v2.1.1", - "sha": "a7805884c80886efc241e94a5351df715968a0ad" - }, - "actions/attest-build-provenance@v4.1.0": { - "repo": "actions/attest-build-provenance", - "version": "v4.1.0", - "sha": "a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32" - }, "actions/cache/restore@v5.0.5": { "repo": "actions/cache/restore", "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, - "actions/cache/save@v5.0.5": { - "repo": "actions/cache/save", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, - "actions/cache@v5.0.5": { - "repo": "actions/cache", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, - "actions/create-github-app-token@v3.2.0": { - "repo": "actions/create-github-app-token", - "version": "v3.2.0", - "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" - }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -68,21 +43,11 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "actions/setup-dotnet@v5.3.0": { - "repo": "actions/setup-dotnet", - "version": "v5.3.0", - "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" - }, "actions/setup-go@v6.4.0": { "repo": "actions/setup-go", "version": "v6.4.0", "sha": "4a3601121dd01d1626a1e23e37211e3254c1c06c" }, - "actions/setup-java@v5.2.0": { - "repo": "actions/setup-java", - "version": "v5.2.0", - "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" - }, "actions/setup-node@v6.4.0": { "repo": "actions/setup-node", "version": "v6.4.0", @@ -103,21 +68,6 @@ "version": "v0.24.0", "sha": "e22c389904149dbc22b58101806040fa8d37a610" }, - "astral-sh/setup-uv@v8.2.0": { - "repo": "astral-sh/setup-uv", - "version": "v8.2.0", - "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" - }, - "cli/gh-extension-precompile@v2.1.0": { - "repo": "cli/gh-extension-precompile", - "version": "v2.1.0", - "sha": "9e2237c30f869ad3bcaed6a4be2cd43564dd421b" - }, - "denoland/setup-deno@v2.0.4": { - "repo": "denoland/setup-deno", - "version": "v2.0.4", - "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" - }, "docker/build-push-action@v7.2.0": { "repo": "docker/build-push-action", "version": "v7.2.0", @@ -140,51 +90,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, - "erlef/setup-beam@v1.24.0": { - "repo": "erlef/setup-beam", - "version": "v1.24.0", - "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" - }, - "github/codeql-action/upload-sarif@v4.36.2": { - "repo": "github/codeql-action/upload-sarif", - "version": "v4.36.2", - "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" - }, - "github/gh-aw-actions/setup@v0.79.8": { - "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" - }, - "github/gh-aw/actions/setup-cli@v0.79.8": { - "repo": "github/gh-aw/actions/setup-cli", - "version": "v0.79.8", - "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" - }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", "sha": "378f317724a25737b846bd9895bacf5726dd72ff" }, - "haskell-actions/setup@v2.11.0": { - "repo": "haskell-actions/setup", - "version": "v2.11.0", - "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" - }, - "microsoft/apm-action@v1.9.1": { - "repo": "microsoft/apm-action", - "version": "v1.9.1", - "sha": "e5650fb81c4b5965090a17bd1ed1956071e95d17" - }, - "oven-sh/setup-bun@v2.2.0": { - "repo": "oven-sh/setup-bun", - "version": "v2.2.0", - "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" - }, - "ruby/setup-ruby@v1.313.0": { - "repo": "ruby/setup-ruby", - "version": "v1.313.0", - "sha": "89f90524b88a01fe6e0b732220432cc6142926af" - }, "safedep/pmg@v1": { "repo": "safedep/pmg", "version": "v1", diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json index 0a684211ed0..da4c5673dd8 100644 --- a/pkg/workflow/data/action_pins.json +++ b/pkg/workflow/data/action_pins.json @@ -23,41 +23,16 @@ }, "action_description": "Add labels to an issue or a pull request." }, - "actions/ai-inference@v2.1.1": { - "repo": "actions/ai-inference", - "version": "v2.1.1", - "sha": "a7805884c80886efc241e94a5351df715968a0ad" - }, - "actions/attest-build-provenance@v4.1.0": { - "repo": "actions/attest-build-provenance", - "version": "v4.1.0", - "sha": "a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32" - }, "actions/cache/restore@v5.0.5": { "repo": "actions/cache/restore", "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, - "actions/cache/save@v5.0.5": { - "repo": "actions/cache/save", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, - "actions/cache@v5.0.5": { - "repo": "actions/cache", - "version": "v5.0.5", - "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" - }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, - "actions/create-github-app-token@v3.2.0": { - "repo": "actions/create-github-app-token", - "version": "v3.2.0", - "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" - }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -68,21 +43,11 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "actions/setup-dotnet@v5.3.0": { - "repo": "actions/setup-dotnet", - "version": "v5.3.0", - "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" - }, "actions/setup-go@v6.4.0": { "repo": "actions/setup-go", "version": "v6.4.0", "sha": "4a3601121dd01d1626a1e23e37211e3254c1c06c" }, - "actions/setup-java@v5.2.0": { - "repo": "actions/setup-java", - "version": "v5.2.0", - "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" - }, "actions/setup-node@v6.4.0": { "repo": "actions/setup-node", "version": "v6.4.0", @@ -103,21 +68,6 @@ "version": "v0.24.0", "sha": "e22c389904149dbc22b58101806040fa8d37a610" }, - "astral-sh/setup-uv@v8.2.0": { - "repo": "astral-sh/setup-uv", - "version": "v8.2.0", - "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" - }, - "cli/gh-extension-precompile@v2.1.0": { - "repo": "cli/gh-extension-precompile", - "version": "v2.1.0", - "sha": "9e2237c30f869ad3bcaed6a4be2cd43564dd421b" - }, - "denoland/setup-deno@v2.0.4": { - "repo": "denoland/setup-deno", - "version": "v2.0.4", - "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" - }, "docker/build-push-action@v7.2.0": { "repo": "docker/build-push-action", "version": "v7.2.0", @@ -140,51 +90,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, - "erlef/setup-beam@v1.24.0": { - "repo": "erlef/setup-beam", - "version": "v1.24.0", - "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" - }, - "github/codeql-action/upload-sarif@v4.36.2": { - "repo": "github/codeql-action/upload-sarif", - "version": "v4.36.2", - "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" - }, - "github/gh-aw-actions/setup@v0.79.8": { - "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" - }, - "github/gh-aw/actions/setup-cli@v0.79.8": { - "repo": "github/gh-aw/actions/setup-cli", - "version": "v0.79.8", - "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" - }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", "sha": "378f317724a25737b846bd9895bacf5726dd72ff" }, - "haskell-actions/setup@v2.11.0": { - "repo": "haskell-actions/setup", - "version": "v2.11.0", - "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" - }, - "microsoft/apm-action@v1.9.1": { - "repo": "microsoft/apm-action", - "version": "v1.9.1", - "sha": "e5650fb81c4b5965090a17bd1ed1956071e95d17" - }, - "oven-sh/setup-bun@v2.2.0": { - "repo": "oven-sh/setup-bun", - "version": "v2.2.0", - "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" - }, - "ruby/setup-ruby@v1.313.0": { - "repo": "ruby/setup-ruby", - "version": "v1.313.0", - "sha": "89f90524b88a01fe6e0b732220432cc6142926af" - }, "safedep/pmg@v1": { "repo": "safedep/pmg", "version": "v1", From 4144995e08c19a5aeec8dc1b5863bd0501316c9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 23:45:05 +0000 Subject: [PATCH 6/9] fix: harden orphaned cache pruning safeguards Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_pipeline.go | 7 ++++--- pkg/cli/compile_post_processing.go | 7 +++++-- pkg/workflow/action_resolver.go | 8 +++++--- pkg/workflow/action_resolver_test.go | 25 +++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index 6f59e257094..66c5c723a89 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -433,7 +433,7 @@ func compileAllFilesInDirectory( } // Post-processing - if err := runPostProcessingForDirectory(ctx, compiler, workflowDataList, config, workflowsDir, gitRoot, successCount); err != nil { + if err := runPostProcessingForDirectory(ctx, compiler, workflowDataList, config, workflowsDir, gitRoot, successCount, errorCount); err != nil { return workflowDataList, err } @@ -551,6 +551,7 @@ func runPostProcessingForDirectory( workflowsDir string, gitRoot string, successCount int, + errorCount int, ) error { // Get action cache actionCache := compiler.GetSharedActionCache() @@ -600,8 +601,8 @@ func runPostProcessingForDirectory( // Prune orphaned entries — entries for action versions no longer referenced // by any workflow in the directory (e.g. old pins left after a version bump). - // Safe to call only after a full-directory compilation (all workflows compiled). - pruneOrphanedActionCacheEntries(compiler, actionCache) + // Safe to call only after a full-directory compilation with zero compile errors. + pruneOrphanedActionCacheEntries(compiler, actionCache, errorCount) // Save action cache (errors are logged but non-fatal) _ = saveActionCache(actionCache, config.Verbose) diff --git a/pkg/cli/compile_post_processing.go b/pkg/cli/compile_post_processing.go index 91c6a4aaa51..ff96df5d6c9 100644 --- a/pkg/cli/compile_post_processing.go +++ b/pkg/cli/compile_post_processing.go @@ -306,11 +306,14 @@ func pruneStaleActionCacheEntries(compiler *workflow.Compiler, actionCache *work // // This is only safe to call after a full-directory compilation — compiling a // subset of files would incorrectly prune entries still referenced by other -// (uncompiled) workflows. -func pruneOrphanedActionCacheEntries(compiler *workflow.Compiler, actionCache *workflow.ActionCache) { +// (uncompiled) workflows — and only when there were zero compile errors. +func pruneOrphanedActionCacheEntries(compiler *workflow.Compiler, actionCache *workflow.ActionCache, errorCount int) { if actionCache == nil { return } + if errorCount > 0 { + return + } resolver := compiler.GetSharedActionResolver() if resolver == nil { diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index bf190a69d7c..acf478a6678 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -3,6 +3,7 @@ package workflow import ( "context" "fmt" + "maps" "strings" "time" @@ -34,7 +35,9 @@ func NewActionResolver(cache *ActionCache) *ActionResolver { // were successfully resolved from the cache or written to the cache during this run. // These represent the action pins actually referenced by the compiled workflows. func (r *ActionResolver) GetUsedCacheKeys() map[string]bool { - return r.usedCacheKeys + keys := make(map[string]bool, len(r.usedCacheKeys)) + maps.Copy(keys, r.usedCacheKeys) + return keys } // ResolveSHA resolves the SHA for a given action@version using GitHub CLI @@ -45,6 +48,7 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Create a cache key for tracking failed resolutions and cache lookups. // Computed once here and reused below to avoid duplicate allocation. cacheKey := formatActionCacheKey(repo, version) + r.usedCacheKeys[cacheKey] = true // Check if we've already failed to resolve this action in this run if r.failedResolutions[cacheKey] { @@ -55,7 +59,6 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Check cache first using the pre-computed key to avoid a second key allocation. if sha, found := r.cache.GetByCacheKey(cacheKey); found { resolverLog.Printf("Cache hit for %s@%s: %s", repo, version, sha) - r.usedCacheKeys[cacheKey] = true return sha, nil } @@ -105,7 +108,6 @@ func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) ( // Cache the result resolverLog.Printf("Caching result: %s@%s → %s", repo, version, sha) r.cache.Set(repo, version, sha) - r.usedCacheKeys[cacheKey] = true return sha, nil } diff --git a/pkg/workflow/action_resolver_test.go b/pkg/workflow/action_resolver_test.go index 2deb394de52..8f59f79d558 100644 --- a/pkg/workflow/action_resolver_test.go +++ b/pkg/workflow/action_resolver_test.go @@ -90,6 +90,9 @@ func TestActionResolverFailedResolutionCache(t *testing.T) { if !resolver.failedResolutions[cacheKey] { t.Errorf("Expected failed resolution to be tracked for %s", cacheKey) } + if !resolver.GetUsedCacheKeys()[cacheKey] { + t.Errorf("Expected used cache keys to track attempted resolution for %s", cacheKey) + } // Second attempt should be skipped and return error immediately _, err2 := resolver.ResolveSHA(context.Background(), repo, version) @@ -102,6 +105,9 @@ func TestActionResolverFailedResolutionCache(t *testing.T) { if !strings.Contains(err2.Error(), expectedErrMsg) { t.Errorf("Expected error message to contain %q, got: %v", expectedErrMsg, err2) } + if !resolver.GetUsedCacheKeys()[cacheKey] { + t.Errorf("Expected used cache keys to retain attempted resolution key %s", cacheKey) + } } // Note: Testing the actual GitHub API resolution requires network access @@ -219,6 +225,7 @@ func TestActionResolverUsedCacheKeysOnCacheHit(t *testing.T) { if err != nil { t.Fatalf("Expected no error for cached entry, got: %v", err) } + if sha != "sha_a" { t.Errorf("Expected sha_a, got %q", sha) } @@ -231,3 +238,21 @@ func TestActionResolverUsedCacheKeysOnCacheHit(t *testing.T) { t.Error("Expected owner/action-b@v2 to be absent from used cache keys (never resolved)") } } + +func TestActionResolverGetUsedCacheKeysReturnsCopy(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + resolver := NewActionResolver(cache) + cache.Set("owner/action-a", "v1", "sha_a") + + if _, err := resolver.ResolveSHA(context.Background(), "owner/action-a", "v1"); err != nil { + t.Fatalf("Expected no error resolving cache hit: %v", err) + } + + usedKeys := resolver.GetUsedCacheKeys() + delete(usedKeys, "owner/action-a@v1") + + if !resolver.GetUsedCacheKeys()["owner/action-a@v1"] { + t.Error("Expected resolver used cache keys to be immutable via returned map") + } +} From 759b4793240e22da1fcfba0f51f9dfac4b5b9574 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:29:13 +0000 Subject: [PATCH 7/9] fix: preserve compiler-generated actions during orphaned entry pruning Compiler-generated actions (actions/cache/*, actions/checkout, actions/github-script, github/codeql-action/upload-sarif) are embedded in Go code rather than markdown workflows, so they won't be tracked as "used" during normal compilation. These actions must never be pruned because they're always potentially needed by the compiler. Updated PruneOrphanedEntries to skip compiler-generated action repos regardless of referenced set. Added test coverage and updated existing test that assumed actions/checkout could be pruned. Restored actions-lock.json from main to ensure embedded pins contain compiler-generated actions (they were previously pruned, breaking tests). Fixes the CI failure at: https://github.com/github/gh-aw/actions/runs/27724121408/job/82016442512?pr=39905 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/actions-lock.json | 20 +++ .github/workflows/agentic_commands.yml | 134 +++++++++++++++++++++ .github/workflows/agentics-maintenance.yml | 5 +- pkg/actionpins/data/action_pins.json | 20 +++ pkg/workflow/action_cache.go | 45 ++++++- pkg/workflow/action_cache_test.go | 44 ++++++- pkg/workflow/action_pins.go | 20 +++ pkg/workflow/action_resolver.go | 48 ++++++++ pkg/workflow/compiler.go | 7 ++ pkg/workflow/create_code_scanning_alert.go | 2 +- pkg/workflow/data/action_pins.json | 20 +++ 11 files changed, 359 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/agentic_commands.yml diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index da4c5673dd8..be60ccbecc6 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -28,11 +28,26 @@ "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, + "actions/cache/save@v5.0.5": { + "repo": "actions/cache/save", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, + "actions/cache@v5.0.5": { + "repo": "actions/cache", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, + "actions/create-github-app-token@v3.2.0": { + "repo": "actions/create-github-app-token", + "version": "v3.2.0", + "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" + }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -90,6 +105,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, + "github/codeql-action/upload-sarif@v4.36.2": { + "repo": "github/codeql-action/upload-sarif", + "version": "v4.36.2", + "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" + }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", diff --git a/.github/workflows/agentic_commands.yml b/.github/workflows/agentic_commands.yml new file mode 100644 index 00000000000..e46cbfbcd3a --- /dev/null +++ b/.github/workflows/agentic_commands.yml @@ -0,0 +1,134 @@ +# gh-aw-commands: {"payload_version":"v1","schema_version":"v1","compiler_version":"dev","commands":["ace","approach-validator","archie","brave","cloclo","craft","grumpy","matt","mergefest","nit","plan","poem-bot","review","ruflo","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-antigravity","smoke-call-workflow","smoke-claude","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-sdk","smoke-create-cross-repo-pr","smoke-crush","smoke-gemini","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","summarize","tidy","unbloat"],"workflows":["ace-editor","approach-validator","archie","brave","ci-doctor","cloclo","craft","design-decision-gate","dev","grumpy-reviewer","mattpocock-skills-reviewer","mergefest","necromancer","pdf-summary","plan","poem-bot","pr-code-quality-reviewer","pr-nitpick-reviewer","ruflo-backed-task","scout","security-review","smoke-agent-all-merged","smoke-agent-all-none","smoke-agent-public-approved","smoke-agent-public-none","smoke-agent-scoped-approved","smoke-antigravity","smoke-call-workflow","smoke-claude","smoke-codex","smoke-copilot","smoke-copilot-aoai-apikey","smoke-copilot-aoai-entra","smoke-copilot-arm","smoke-copilot-sdk","smoke-create-cross-repo-pr","smoke-crush","smoke-gemini","smoke-multi-pr","smoke-opencode","smoke-otel-backends","smoke-pi","smoke-project","smoke-service-ports","smoke-temporary-id","smoke-test-tools","smoke-update-cross-repo-pr","test-quality-sentinel","tidy","unbloat-docs"]} +# Routing summary (sorted): +# slash commands: +# /ace -> ace-editor [pull_request_comment] reaction=eyes +# /approach-validator -> approach-validator [issue_comment,pull_request_comment] reaction=eyes +# /archie -> archie [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /brave -> brave [issue_comment] reaction=eyes +# /cloclo -> cloclo [discussion,discussion_comment,issue_comment,issues,pull_request,pull_request_comment,pull_request_review_comment] reaction=eyes +# /craft -> craft [issues] reaction=eyes +# /grumpy -> grumpy-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes +# /matt -> mattpocock-skills-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes +# /mergefest -> mergefest [pull_request_comment] reaction=eyes +# /nit -> pr-nitpick-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes +# /plan -> plan [discussion_comment,issue_comment] reaction=eyes +# /poem-bot -> poem-bot [issues] reaction=eyes +# /review -> design-decision-gate [pull_request_comment,pull_request_review_comment] reaction=eyes +# /review -> pr-code-quality-reviewer [pull_request_comment,pull_request_review_comment] reaction=eyes +# /review -> test-quality-sentinel [pull_request_comment,pull_request_review_comment] reaction=eyes +# /ruflo -> ruflo-backed-task [issue_comment] reaction=eyes +# /scout -> scout [discussion,discussion_comment,issue_comment,issues,pull_request,pull_request_comment,pull_request_review_comment] reaction=eyes +# /security-review -> security-review [pull_request_comment,pull_request_review_comment] reaction=eyes +# /smoke-agent-all-merged -> smoke-agent-all-merged [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-agent-all-none -> smoke-agent-all-none [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-agent-public-approved -> smoke-agent-public-approved [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-agent-public-none -> smoke-agent-public-none [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-agent-scoped-approved -> smoke-agent-scoped-approved [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-antigravity -> smoke-antigravity [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket +# /smoke-call-workflow -> smoke-call-workflow [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-claude -> smoke-claude [issue_comment,issues,pull_request,pull_request_comment] reaction=heart +# /smoke-codex -> smoke-codex [issue_comment,issues,pull_request,pull_request_comment] reaction=hooray +# /smoke-copilot -> smoke-copilot [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-copilot-aoai-apikey -> smoke-copilot-aoai-apikey [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-copilot-aoai-entra -> smoke-copilot-aoai-entra [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-copilot-arm -> smoke-copilot-arm [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-copilot-sdk -> smoke-copilot-sdk [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-create-cross-repo-pr -> smoke-create-cross-repo-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-crush -> smoke-crush [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-gemini -> smoke-gemini [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket +# /smoke-multi-pr -> smoke-multi-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-opencode -> smoke-opencode [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket +# /smoke-otel-backends -> smoke-otel-backends [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-pi -> smoke-pi [issue_comment,issues,pull_request,pull_request_comment] reaction=rocket +# /smoke-project -> smoke-project [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-service-ports -> smoke-service-ports [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-temporary-id -> smoke-temporary-id [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-test-tools -> smoke-test-tools [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /smoke-update-cross-repo-pr -> smoke-update-cross-repo-pr [issue_comment,issues,pull_request,pull_request_comment] reaction=eyes +# /summarize -> pdf-summary [issue_comment,issues] reaction=eyes +# /tidy -> tidy [pull_request_comment] reaction=eyes +# /unbloat -> unbloat-docs [pull_request_comment] reaction=eyes +# labels: +# approach-proposal -> approach-validator [issues,pull_request] reaction=eyes +# ci-doctor -> ci-doctor [pull_request] reaction=eyes +# cloclo -> cloclo [discussion,issues,pull_request] reaction=eyes +# dev -> dev [discussion,issues,pull_request] reaction=eyes +# necromancer -> necromancer [pull_request] reaction=eyes +# needs-design -> approach-validator [issues,pull_request] reaction=eyes +# smoke -> smoke-copilot [pull_request] reaction=eyes +# smoke -> smoke-copilot-aoai-apikey [pull_request] reaction=eyes +# smoke -> smoke-copilot-aoai-entra [pull_request] reaction=eyes +# smoke -> smoke-otel-backends [pull_request] reaction=eyes +# smoke-sdk -> smoke-copilot-sdk [pull_request] reaction=eyes +# This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +name: "Agentic Commands" + +on: + issues: + types: [edited, labeled, opened, reopened] + issue_comment: + types: [created, edited] + pull_request: + types: [edited, labeled, opened, reopened] + pull_request_review_comment: + types: [created, edited] + discussion: + types: [created, edited, labeled] + discussion_comment: + types: [created, edited] + +permissions: {} + +jobs: + route: + runs-on: ubuntu-slim + timeout-minutes: 15 + permissions: + actions: write + contents: read + issues: write + pull-requests: write + discussions: write + + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + + - name: Setup Scripts + uses: ./actions/setup + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Route slash command + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SLASH_ROUTING: '{"ace":[{"workflow":"ace-editor","events":["pull_request_comment"],"ai_reaction":"eyes"}],"approach-validator":[{"workflow":"approach-validator","events":["issue_comment","pull_request_comment"],"ai_reaction":"eyes"}],"archie":[{"workflow":"archie","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"brave":[{"workflow":"brave","events":["issue_comment"],"ai_reaction":"eyes"}],"cloclo":[{"workflow":"cloclo","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"craft":[{"workflow":"craft","events":["issues"],"ai_reaction":"eyes"}],"grumpy":[{"workflow":"grumpy-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"matt":[{"workflow":"mattpocock-skills-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"mergefest":[{"workflow":"mergefest","events":["pull_request_comment"],"ai_reaction":"eyes"}],"nit":[{"workflow":"pr-nitpick-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"plan":[{"workflow":"plan","events":["discussion_comment","issue_comment"],"ai_reaction":"eyes"}],"poem-bot":[{"workflow":"poem-bot","events":["issues"],"ai_reaction":"eyes"}],"review":[{"workflow":"design-decision-gate","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"},{"workflow":"pr-code-quality-reviewer","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"},{"workflow":"test-quality-sentinel","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"ruflo":[{"workflow":"ruflo-backed-task","events":["issue_comment"],"ai_reaction":"eyes"}],"scout":[{"workflow":"scout","events":["discussion","discussion_comment","issue_comment","issues","pull_request","pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"security-review":[{"workflow":"security-review","events":["pull_request_comment","pull_request_review_comment"],"ai_reaction":"eyes"}],"smoke-agent-all-merged":[{"workflow":"smoke-agent-all-merged","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-all-none":[{"workflow":"smoke-agent-all-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-public-approved":[{"workflow":"smoke-agent-public-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-public-none":[{"workflow":"smoke-agent-public-none","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-agent-scoped-approved":[{"workflow":"smoke-agent-scoped-approved","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-antigravity":[{"workflow":"smoke-antigravity","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-call-workflow":[{"workflow":"smoke-call-workflow","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-claude":[{"workflow":"smoke-claude","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"heart"}],"smoke-codex":[{"workflow":"smoke-codex","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"hooray"}],"smoke-copilot":[{"workflow":"smoke-copilot","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-aoai-apikey":[{"workflow":"smoke-copilot-aoai-apikey","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-aoai-entra":[{"workflow":"smoke-copilot-aoai-entra","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-arm":[{"workflow":"smoke-copilot-arm","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-copilot-sdk":[{"workflow":"smoke-copilot-sdk","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-create-cross-repo-pr":[{"workflow":"smoke-create-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-crush":[{"workflow":"smoke-crush","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-gemini":[{"workflow":"smoke-gemini","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-multi-pr":[{"workflow":"smoke-multi-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-opencode":[{"workflow":"smoke-opencode","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-otel-backends":[{"workflow":"smoke-otel-backends","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-pi":[{"workflow":"smoke-pi","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"rocket"}],"smoke-project":[{"workflow":"smoke-project","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-service-ports":[{"workflow":"smoke-service-ports","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-temporary-id":[{"workflow":"smoke-temporary-id","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-test-tools":[{"workflow":"smoke-test-tools","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"smoke-update-cross-repo-pr":[{"workflow":"smoke-update-cross-repo-pr","events":["issue_comment","issues","pull_request","pull_request_comment"],"ai_reaction":"eyes"}],"summarize":[{"workflow":"pdf-summary","events":["issue_comment","issues"],"ai_reaction":"eyes"}],"tidy":[{"workflow":"tidy","events":["pull_request_comment"],"ai_reaction":"eyes"}],"unbloat":[{"workflow":"unbloat-docs","events":["pull_request_comment"],"ai_reaction":"eyes"}]}' + GH_AW_LABEL_ROUTING: '{"approach-proposal":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes"}],"ci-doctor":[{"workflow":"ci-doctor","events":["pull_request"],"ai_reaction":"eyes"}],"cloclo":[{"workflow":"cloclo","events":["discussion","issues","pull_request"],"ai_reaction":"eyes"}],"dev":[{"workflow":"dev","events":["discussion","issues","pull_request"],"ai_reaction":"eyes"}],"necromancer":[{"workflow":"necromancer","events":["pull_request"],"ai_reaction":"eyes"}],"needs-design":[{"workflow":"approach-validator","events":["issues","pull_request"],"ai_reaction":"eyes"}],"smoke":[{"workflow":"smoke-copilot","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-copilot-aoai-apikey","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-copilot-aoai-entra","events":["pull_request"],"ai_reaction":"eyes"},{"workflow":"smoke-otel-backends","events":["pull_request"],"ai_reaction":"eyes"}],"smoke-sdk":[{"workflow":"smoke-copilot-sdk","events":["pull_request"],"ai_reaction":"eyes"}]}' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/route_slash_command.cjs'); + await main(); diff --git a/.github/workflows/agentics-maintenance.yml b/.github/workflows/agentics-maintenance.yml index b71b17f3e21..6dc884a6868 100644 --- a/.github/workflows/agentics-maintenance.yml +++ b/.github/workflows/agentics-maintenance.yml @@ -431,7 +431,7 @@ jobs: - name: Save activity report logs cache if: ${{ always() }} - uses: + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.cache/gh-aw/activity-report-logs key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} @@ -562,7 +562,7 @@ jobs: - name: Save forecast report logs cache if: ${{ always() }} - uses: + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ./.github/aw/logs key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} @@ -833,7 +833,6 @@ jobs: GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} GH_AW_PROJECT_GITHUB_TOKEN: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }} GH_AW_COPILOT_TOKEN: ${{ secrets.GH_AW_COPILOT_TOKEN }} - GH_AW_COPILOT_ORG_BILLING: "true" # AI Engine API keys ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} diff --git a/pkg/actionpins/data/action_pins.json b/pkg/actionpins/data/action_pins.json index da4c5673dd8..be60ccbecc6 100644 --- a/pkg/actionpins/data/action_pins.json +++ b/pkg/actionpins/data/action_pins.json @@ -28,11 +28,26 @@ "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, + "actions/cache/save@v5.0.5": { + "repo": "actions/cache/save", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, + "actions/cache@v5.0.5": { + "repo": "actions/cache", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, + "actions/create-github-app-token@v3.2.0": { + "repo": "actions/create-github-app-token", + "version": "v3.2.0", + "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" + }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -90,6 +105,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, + "github/codeql-action/upload-sarif@v4.36.2": { + "repo": "github/codeql-action/upload-sarif", + "version": "v4.36.2", + "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" + }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", diff --git a/pkg/workflow/action_cache.go b/pkg/workflow/action_cache.go index 7f57ce0a8d1..9b470285470 100644 --- a/pkg/workflow/action_cache.go +++ b/pkg/workflow/action_cache.go @@ -105,9 +105,28 @@ func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { if len(referencedKeys) == 0 { return 0 } + + // Compiler-generated actions that should never be pruned + // These are embedded in Go code rather than markdown workflows + compilerGeneratedRepos := []string{ + "actions/cache/", + "actions/checkout", + "actions/github-script", + "github/codeql-action/upload-sarif", + } + + isCompilerGenerated := func(cacheKey string) bool { + for _, repo := range compilerGeneratedRepos { + if strings.HasPrefix(cacheKey, repo) { + return true + } + } + return false + } + pruned := 0 for key := range c.Entries { - if !referencedKeys[key] { + if !referencedKeys[key] && !isCompilerGenerated(key) { delete(c.Entries, key) c.dirty = true pruned++ @@ -377,6 +396,30 @@ func (c *ActionCache) FindEntryBySHA(repo, sha string) (ActionCacheEntry, bool) return ActionCacheEntry{}, false } +// FindAnyEntryForRepo finds any cache entry for the given repo, +// preferring the newest version (by sorting keys and taking first match). +// Returns the cache key, entry, and true if found, or empty values and false if not found. +// This is used when the compiler needs to reference an action but doesn't know the version. +func (c *ActionCache) FindAnyEntryForRepo(repo string) (string, ActionCacheEntry, bool) { + prefix := repo + "@" + var matchedKeys []string + for key := range c.Entries { + if strings.HasPrefix(key, prefix) { + matchedKeys = append(matchedKeys, key) + } + } + if len(matchedKeys) == 0 { + actionCacheLog.Printf("No cache entries found for repo: %s", repo) + return "", ActionCacheEntry{}, false + } + // Sort keys and take the first one (lexicographically, which tends to favor newer versions) + sort.Strings(matchedKeys) + firstKey := matchedKeys[len(matchedKeys)-1] // Take the last one for descending order (v9 > v1) + entry := c.Entries[firstKey] + actionCacheLog.Printf("Found cache entry for %s: %s", repo, firstKey) + return firstKey, entry, true +} + // Set stores a new cache entry, preserving any already-cached inputs when the SHA // is unchanged. If the SHA changes (e.g. a moving tag points to a new commit), // cached inputs are cleared to stay consistent with the newly-pinned commit. diff --git a/pkg/workflow/action_cache_test.go b/pkg/workflow/action_cache_test.go index a1bc7941d79..764a5ca6aa6 100644 --- a/pkg/workflow/action_cache_test.go +++ b/pkg/workflow/action_cache_test.go @@ -970,7 +970,7 @@ func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { tmpDir := testutil.TempDir(t, "test-*") cache := NewActionCache(tmpDir) - cache.Set("actions/checkout", "v4", "sha1") + cache.Set("microsoft/apm-action", "v1.7.2", "sha1") cache.Set("actions/setup-node", "v4", "sha2") // Referenced set is non-empty but contains neither of the cached entries. @@ -986,3 +986,45 @@ func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { t.Errorf("Expected 0 entries after pruning all orphans, got %d", len(cache.Entries)) } } + +// TestPruneOrphanedEntries_PreservesCompilerGenerated verifies that compiler-generated +// actions are never pruned, even when not in the referenced set. +func TestPruneOrphanedEntries_PreservesCompilerGenerated(t *testing.T) { + tmpDir := testutil.TempDir(t, "test-*") + cache := NewActionCache(tmpDir) + + // Add compiler-generated actions and a regular action + cache.Set("actions/cache/save", "v4", "sha_cache_save") + cache.Set("actions/checkout", "v4", "sha_checkout") + cache.Set("github/codeql-action/upload-sarif", "v4", "sha_codeql") + cache.Set("microsoft/apm-action", "v1.7.2", "sha_old") + + if len(cache.Entries) != 4 { + t.Fatalf("Expected 4 entries before pruning, got %d", len(cache.Entries)) + } + + // Only reference microsoft/apm-action, but not the compiler-generated ones + referenced := map[string]bool{ + "microsoft/apm-action@v1.7.2": true, + } + pruned := cache.PruneOrphanedEntries(referenced) + + // Should not prune compiler-generated actions + if pruned != 0 { + t.Errorf("Expected 0 pruned entries (compiler-generated actions preserved), got %d", pruned) + } + if len(cache.Entries) != 4 { + t.Errorf("Expected 4 entries after pruning (all preserved), got %d", len(cache.Entries)) + } + + // Verify compiler-generated actions are preserved + if _, exists := cache.Entries["actions/cache/save@v4"]; !exists { + t.Error("Expected compiler-generated actions/cache/save@v4 to be preserved") + } + if _, exists := cache.Entries["actions/checkout@v4"]; !exists { + t.Error("Expected compiler-generated actions/checkout@v4 to be preserved") + } + if _, exists := cache.Entries["github/codeql-action/upload-sarif@v4"]; !exists { + t.Error("Expected compiler-generated github/codeql-action/upload-sarif@v4 to be preserved") + } +} diff --git a/pkg/workflow/action_pins.go b/pkg/workflow/action_pins.go index 3c1b5c2afa5..d1ff2ab83b7 100644 --- a/pkg/workflow/action_pins.go +++ b/pkg/workflow/action_pins.go @@ -85,6 +85,10 @@ func getActionPin(repo string) string { // // This is the preferred call site for code running inside a Compiler method, since it // automatically honours the per-compilation GHES compat flag without any global state. +// +// If the compiler has an action cache and resolver, this method will check the cache for +// any existing entry and mark it as "used" for orphan pruning. This ensures compiler-generated +// action references (e.g., actions/cache/save in notify steps) are tracked. func (c *Compiler) getActionPin(repo string) string { if c.ghesArtifactCompat { if pin, ok := ghesArtifactCompatPins[repo]; ok { @@ -92,6 +96,22 @@ func (c *Compiler) getActionPin(repo string) string { return actionpins.FormatPinnedActionReference(repo, pin.sha, pin.version) } } + + // Check the cache for any existing entry for this repo (regardless of version). + // Compiler-generated actions don't specify versions, so we use any cached entry we have. + cache := c.GetSharedActionCache() + resolver := c.GetSharedActionResolver() + if cache != nil { + if cacheKey, entry, found := cache.FindAnyEntryForRepo(repo); found { + // Mark this cache key as used so it won't be pruned as orphaned + if resolver != nil { + resolver.MarkCacheKeyAsUsed(cacheKey) + } + return actionpins.FormatPinnedActionReference(repo, entry.SHA, entry.Version) + } + } + + // Fall back to embedded pins if no cache entry exists return getActionPin(repo) } diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index acf478a6678..15c9ae02820 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -40,6 +40,54 @@ func (r *ActionResolver) GetUsedCacheKeys() map[string]bool { return keys } +// MarkCacheKeyAsUsed explicitly marks a cache key as used during this compilation run. +// This is useful for compiler-generated actions that aren't resolved through ResolveSHA. +func (r *ActionResolver) MarkCacheKeyAsUsed(cacheKey string) { + r.usedCacheKeys[cacheKey] = true + resolverLog.Printf("Marked cache key as used: %s", cacheKey) +} + +// MarkCompilerGeneratedActionsAsUsed scans the cache for any entries matching +// compiler-generated action repos and marks them as used. This ensures that actions +// embedded in generated YAML (e.g., actions/cache, actions/checkout) aren't pruned +// as orphaned entries even if they weren't resolved through ResolveSHA. +// +// This is called after workflow compilation to handle actions that are hardcoded +// in code generators (cache.go, checkout_step_generator.go, etc.) rather than +// resolved from markdown workflows. +func (r *ActionResolver) MarkCompilerGeneratedActionsAsUsed() { + if r.cache == nil { + return + } + + // List of action repos that are commonly generated by the compiler + compilerGeneratedRepos := []string{ + "actions/cache", + "actions/cache/restore", + "actions/cache/save", + "actions/checkout", + "actions/github-script", + "actions/upload-artifact", + "actions/download-artifact", + "actions/create-github-app-token", + "github/codeql-action/upload-sarif", + } + + marked := 0 + for _, repo := range compilerGeneratedRepos { + if cacheKey, _, found := r.cache.FindAnyEntryForRepo(repo); found { + if !r.usedCacheKeys[cacheKey] { + r.usedCacheKeys[cacheKey] = true + marked++ + resolverLog.Printf("Marked compiler-generated action as used: %s", cacheKey) + } + } + } + if marked > 0 { + resolverLog.Printf("Marked %d compiler-generated action(s) as used", marked) + } +} + // ResolveSHA resolves the SHA for a given action@version using GitHub CLI // Returns the SHA and an error if resolution fails func (r *ActionResolver) ResolveSHA(ctx context.Context, repo, version string) (string, error) { diff --git a/pkg/workflow/compiler.go b/pkg/workflow/compiler.go index 3f6b8014fb5..48c1359eae0 100644 --- a/pkg/workflow/compiler.go +++ b/pkg/workflow/compiler.go @@ -530,6 +530,13 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath } } + // Mark compiler-generated actions as used to prevent pruning. + // This handles actions that are hardcoded in code generators (cache.go, + // checkout_step_generator.go, etc.) rather than resolved from markdown workflows. + if resolver := c.GetSharedActionResolver(); resolver != nil { + resolver.MarkCompilerGeneratedActionsAsUsed() + } + // Write output if err := c.writeWorkflowOutput(lockFile, yamlContent, markdownPath); err != nil { return err diff --git a/pkg/workflow/create_code_scanning_alert.go b/pkg/workflow/create_code_scanning_alert.go index 44b4fbff0f0..09586f87fa2 100644 --- a/pkg/workflow/create_code_scanning_alert.go +++ b/pkg/workflow/create_code_scanning_alert.go @@ -143,7 +143,7 @@ func (c *Compiler) buildCodeScanningUploadJob(data *WorkflowData) (*Job, error) // Step: Upload SARIF file to GitHub Code Scanning. steps = append(steps, " - name: Upload SARIF to GitHub Code Scanning\n") steps = append(steps, fmt.Sprintf(" id: %s\n", constants.UploadCodeScanningJobName)) - steps = append(steps, fmt.Sprintf(" uses: %s\n", getActionPin("github/codeql-action/upload-sarif"))) + steps = append(steps, fmt.Sprintf(" uses: %s\n", c.getActionPin("github/codeql-action/upload-sarif"))) steps = append(steps, " with:\n") // NOTE: github/codeql-action/upload-sarif uses 'token' as the input name, not 'github-token' // Pass restoreToken as the fallback so GitHub App-minted tokens flow through consistently. diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json index da4c5673dd8..be60ccbecc6 100644 --- a/pkg/workflow/data/action_pins.json +++ b/pkg/workflow/data/action_pins.json @@ -28,11 +28,26 @@ "version": "v5.0.5", "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" }, + "actions/cache/save@v5.0.5": { + "repo": "actions/cache/save", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, + "actions/cache@v5.0.5": { + "repo": "actions/cache", + "version": "v5.0.5", + "sha": "27d5ce7f107fe9357f9df03efb73ab90386fccae" + }, "actions/checkout@v6.0.3": { "repo": "actions/checkout", "version": "v6.0.3", "sha": "df4cb1c069e1874edd31b4311f1884172cec0e10" }, + "actions/create-github-app-token@v3.2.0": { + "repo": "actions/create-github-app-token", + "version": "v3.2.0", + "sha": "bcd2ba49218906704ab6c1aa796996da409d3eb1" + }, "actions/download-artifact@v8.0.1": { "repo": "actions/download-artifact", "version": "v8.0.1", @@ -90,6 +105,11 @@ "version": "v4.1.0", "sha": "d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5" }, + "github/codeql-action/upload-sarif@v4.36.2": { + "repo": "github/codeql-action/upload-sarif", + "version": "v4.36.2", + "sha": "8aad20d150bbac5944a9f9d289da16a4b0d87c1e" + }, "github/stale-repos@v9.0.14": { "repo": "github/stale-repos", "version": "v9.0.14", From ae5c619a8bf3fdb9ee294af29c3e606fbedb6cd6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:45:02 +0000 Subject: [PATCH 8/9] fix: preserve runtime-managed actions during orphaned entry pruning Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/actions-lock.json | 45 ++++++++++++++++++++++++++++ pkg/actionpins/data/action_pins.json | 45 ++++++++++++++++++++++++++++ pkg/workflow/action_cache.go | 14 +++++++-- pkg/workflow/action_cache_test.go | 34 +++++++++++++++------ pkg/workflow/data/action_pins.json | 45 ++++++++++++++++++++++++++++ 5 files changed, 172 insertions(+), 11 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index be60ccbecc6..e12149a652f 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -124,6 +124,51 @@ "repo": "super-linter/super-linter", "version": "v8.6.0", "sha": "9e863354e3ff62e0727d37183162c4a88873df41" + }, + "actions/setup-dotnet@v5.3.0": { + "repo": "actions/setup-dotnet", + "version": "v5.3.0", + "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" + }, + "actions/setup-java@v5.2.0": { + "repo": "actions/setup-java", + "version": "v5.2.0", + "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" + }, + "astral-sh/setup-uv@v8.2.0": { + "repo": "astral-sh/setup-uv", + "version": "v8.2.0", + "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" + }, + "denoland/setup-deno@v2.0.4": { + "repo": "denoland/setup-deno", + "version": "v2.0.4", + "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" + }, + "erlef/setup-beam@v1.24.0": { + "repo": "erlef/setup-beam", + "version": "v1.24.0", + "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" + }, + "github/gh-aw/actions/setup-cli@v0.79.8": { + "repo": "github/gh-aw/actions/setup-cli", + "version": "v0.79.8", + "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" + }, + "haskell-actions/setup@v2.11.0": { + "repo": "haskell-actions/setup", + "version": "v2.11.0", + "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" + }, + "oven-sh/setup-bun@v2.2.0": { + "repo": "oven-sh/setup-bun", + "version": "v2.2.0", + "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" + }, + "ruby/setup-ruby@v1.313.0": { + "repo": "ruby/setup-ruby", + "version": "v1.313.0", + "sha": "89f90524b88a01fe6e0b732220432cc6142926af" } }, "containers": { diff --git a/pkg/actionpins/data/action_pins.json b/pkg/actionpins/data/action_pins.json index be60ccbecc6..e12149a652f 100644 --- a/pkg/actionpins/data/action_pins.json +++ b/pkg/actionpins/data/action_pins.json @@ -124,6 +124,51 @@ "repo": "super-linter/super-linter", "version": "v8.6.0", "sha": "9e863354e3ff62e0727d37183162c4a88873df41" + }, + "actions/setup-dotnet@v5.3.0": { + "repo": "actions/setup-dotnet", + "version": "v5.3.0", + "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" + }, + "actions/setup-java@v5.2.0": { + "repo": "actions/setup-java", + "version": "v5.2.0", + "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" + }, + "astral-sh/setup-uv@v8.2.0": { + "repo": "astral-sh/setup-uv", + "version": "v8.2.0", + "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" + }, + "denoland/setup-deno@v2.0.4": { + "repo": "denoland/setup-deno", + "version": "v2.0.4", + "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" + }, + "erlef/setup-beam@v1.24.0": { + "repo": "erlef/setup-beam", + "version": "v1.24.0", + "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" + }, + "github/gh-aw/actions/setup-cli@v0.79.8": { + "repo": "github/gh-aw/actions/setup-cli", + "version": "v0.79.8", + "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" + }, + "haskell-actions/setup@v2.11.0": { + "repo": "haskell-actions/setup", + "version": "v2.11.0", + "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" + }, + "oven-sh/setup-bun@v2.2.0": { + "repo": "oven-sh/setup-bun", + "version": "v2.2.0", + "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" + }, + "ruby/setup-ruby@v1.313.0": { + "repo": "ruby/setup-ruby", + "version": "v1.313.0", + "sha": "89f90524b88a01fe6e0b732220432cc6142926af" } }, "containers": { diff --git a/pkg/workflow/action_cache.go b/pkg/workflow/action_cache.go index 9b470285470..bc847616a78 100644 --- a/pkg/workflow/action_cache.go +++ b/pkg/workflow/action_cache.go @@ -106,8 +106,11 @@ func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { return 0 } - // Compiler-generated actions that should never be pruned - // These are embedded in Go code rather than markdown workflows + // Compiler-generated actions that should never be pruned. + // These are embedded in Go code rather than markdown workflows and include: + // - Core workflow actions (cache, checkout, github-script) + // - Runtime setup actions (from runtime_definitions.go) + // - Security scanning actions (CodeQL) compilerGeneratedRepos := []string{ "actions/cache/", "actions/checkout", @@ -115,6 +118,13 @@ func (c *ActionCache) PruneOrphanedEntries(referencedKeys map[string]bool) int { "github/codeql-action/upload-sarif", } + // Add all runtime-managed actions from runtime_definitions.go + for _, runtime := range knownRuntimes { + if runtime.ActionRepo != "" { + compilerGeneratedRepos = append(compilerGeneratedRepos, runtime.ActionRepo) + } + } + isCompilerGenerated := func(cacheKey string) bool { for _, repo := range compilerGeneratedRepos { if strings.HasPrefix(cacheKey, repo) { diff --git a/pkg/workflow/action_cache_test.go b/pkg/workflow/action_cache_test.go index 764a5ca6aa6..e2fac9e3a72 100644 --- a/pkg/workflow/action_cache_test.go +++ b/pkg/workflow/action_cache_test.go @@ -970,8 +970,9 @@ func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { tmpDir := testutil.TempDir(t, "test-*") cache := NewActionCache(tmpDir) + // Use actions that are NOT runtime-managed or compiler-generated cache.Set("microsoft/apm-action", "v1.7.2", "sha1") - cache.Set("actions/setup-node", "v4", "sha2") + cache.Set("cli/gh-extension-precompile", "v2.1.0", "sha2") // Referenced set is non-empty but contains neither of the cached entries. referenced := map[string]bool{ @@ -979,6 +980,7 @@ func TestPruneOrphanedEntries_AllOrphaned(t *testing.T) { } pruned := cache.PruneOrphanedEntries(referenced) + // Both entries should be pruned (neither is compiler-generated or runtime-managed) if pruned != 2 { t.Errorf("Expected 2 pruned entries, got %d", pruned) } @@ -993,28 +995,31 @@ func TestPruneOrphanedEntries_PreservesCompilerGenerated(t *testing.T) { tmpDir := testutil.TempDir(t, "test-*") cache := NewActionCache(tmpDir) - // Add compiler-generated actions and a regular action + // Add compiler-generated actions, runtime-managed actions, and a regular action cache.Set("actions/cache/save", "v4", "sha_cache_save") cache.Set("actions/checkout", "v4", "sha_checkout") cache.Set("github/codeql-action/upload-sarif", "v4", "sha_codeql") + cache.Set("actions/setup-node", "v6", "sha_node") // runtime-managed + cache.Set("actions/setup-python", "v5", "sha_python") // runtime-managed + cache.Set("ruby/setup-ruby", "v1", "sha_ruby") // runtime-managed cache.Set("microsoft/apm-action", "v1.7.2", "sha_old") - if len(cache.Entries) != 4 { - t.Fatalf("Expected 4 entries before pruning, got %d", len(cache.Entries)) + if len(cache.Entries) != 7 { + t.Fatalf("Expected 7 entries before pruning, got %d", len(cache.Entries)) } - // Only reference microsoft/apm-action, but not the compiler-generated ones + // Only reference microsoft/apm-action, but not the compiler-generated or runtime-managed ones referenced := map[string]bool{ "microsoft/apm-action@v1.7.2": true, } pruned := cache.PruneOrphanedEntries(referenced) - // Should not prune compiler-generated actions + // Should not prune compiler-generated or runtime-managed actions if pruned != 0 { - t.Errorf("Expected 0 pruned entries (compiler-generated actions preserved), got %d", pruned) + t.Errorf("Expected 0 pruned entries (compiler-generated and runtime-managed actions preserved), got %d", pruned) } - if len(cache.Entries) != 4 { - t.Errorf("Expected 4 entries after pruning (all preserved), got %d", len(cache.Entries)) + if len(cache.Entries) != 7 { + t.Errorf("Expected 7 entries after pruning (all preserved), got %d", len(cache.Entries)) } // Verify compiler-generated actions are preserved @@ -1027,4 +1032,15 @@ func TestPruneOrphanedEntries_PreservesCompilerGenerated(t *testing.T) { if _, exists := cache.Entries["github/codeql-action/upload-sarif@v4"]; !exists { t.Error("Expected compiler-generated github/codeql-action/upload-sarif@v4 to be preserved") } + + // Verify runtime-managed actions are preserved + if _, exists := cache.Entries["actions/setup-node@v6"]; !exists { + t.Error("Expected runtime-managed actions/setup-node@v6 to be preserved") + } + if _, exists := cache.Entries["actions/setup-python@v5"]; !exists { + t.Error("Expected runtime-managed actions/setup-python@v5 to be preserved") + } + if _, exists := cache.Entries["ruby/setup-ruby@v1"]; !exists { + t.Error("Expected runtime-managed ruby/setup-ruby@v1 to be preserved") + } } diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json index be60ccbecc6..e12149a652f 100644 --- a/pkg/workflow/data/action_pins.json +++ b/pkg/workflow/data/action_pins.json @@ -124,6 +124,51 @@ "repo": "super-linter/super-linter", "version": "v8.6.0", "sha": "9e863354e3ff62e0727d37183162c4a88873df41" + }, + "actions/setup-dotnet@v5.3.0": { + "repo": "actions/setup-dotnet", + "version": "v5.3.0", + "sha": "9a946fdbd5fb07b82b2f5a4466058b876ab72bb2" + }, + "actions/setup-java@v5.2.0": { + "repo": "actions/setup-java", + "version": "v5.2.0", + "sha": "be666c2fcd27ec809703dec50e508c2fdc7f6654" + }, + "astral-sh/setup-uv@v8.2.0": { + "repo": "astral-sh/setup-uv", + "version": "v8.2.0", + "sha": "fac544c07dec837d0ccb6301d7b5580bf5edae39" + }, + "denoland/setup-deno@v2.0.4": { + "repo": "denoland/setup-deno", + "version": "v2.0.4", + "sha": "667a34cdef165d8d2b2e98dde39547c9daac7282" + }, + "erlef/setup-beam@v1.24.0": { + "repo": "erlef/setup-beam", + "version": "v1.24.0", + "sha": "fc68ffb90438ef2936bbb3251622353b3dcb2f93" + }, + "github/gh-aw/actions/setup-cli@v0.79.8": { + "repo": "github/gh-aw/actions/setup-cli", + "version": "v0.79.8", + "sha": "8b02ab336d100a5746e9f53b8bc2b22878278a6f" + }, + "haskell-actions/setup@v2.11.0": { + "repo": "haskell-actions/setup", + "version": "v2.11.0", + "sha": "cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553" + }, + "oven-sh/setup-bun@v2.2.0": { + "repo": "oven-sh/setup-bun", + "version": "v2.2.0", + "sha": "0c5077e51419868618aeaa5fe8019c62421857d6" + }, + "ruby/setup-ruby@v1.313.0": { + "repo": "ruby/setup-ruby", + "version": "v1.313.0", + "sha": "89f90524b88a01fe6e0b732220432cc6142926af" } }, "containers": { From 319a2afcd99b53c8507303a9150dc277b2c571c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 05:03:01 +0000 Subject: [PATCH 9/9] test: enhance container pin sorting test with explicit order verification Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../action_cache_container_pin_test.go | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/pkg/workflow/action_cache_container_pin_test.go b/pkg/workflow/action_cache_container_pin_test.go index dbac363966c..5327e80796b 100644 --- a/pkg/workflow/action_cache_container_pin_test.go +++ b/pkg/workflow/action_cache_container_pin_test.go @@ -115,6 +115,7 @@ func TestContainerPinMarshalSortedOutput(t *testing.T) { cache := NewActionCache(tmpDir) cache.Set("actions/checkout", "v5", "sha1") cache.SetContainerPin("z-image:latest", "sha256:zzz", "z-image:latest@sha256:zzz") + cache.SetContainerPin("m-image:v2", "sha256:mmm", "m-image:v2@sha256:mmm") cache.SetContainerPin("a-image:latest", "sha256:aaa", "a-image:latest@sha256:aaa") require.NoError(t, cache.Save()) @@ -122,10 +123,29 @@ func TestContainerPinMarshalSortedOutput(t *testing.T) { content, err := os.ReadFile(filepath.Join(tmpDir, ".github", "aw", CacheFileName)) require.NoError(t, err) - // Both container images should appear in the JSON. contentStr := string(content) - assert.Contains(t, contentStr, `"a-image:latest"`, "a-image pin in output") - assert.Contains(t, contentStr, `"z-image:latest"`, "z-image pin in output") + + // Verify that entries appear in alphabetical order by checking their positions + containers := []string{ + "a-image:latest", + "m-image:v2", + "z-image:latest", + } + + lastPos := -1 + for _, container := range containers { + pos := indexOf(contentStr, `"`+container+`"`) + if pos == -1 { + t.Errorf("Container %s not found in cache file", container) + continue + } + if pos < lastPos { + t.Errorf("Container %s appears before previous container (not sorted)", container) + } + lastPos = pos + } + + // Verify containers section is present assert.Contains(t, contentStr, `"containers"`, "containers section present") // Reload and verify round-trip. @@ -134,6 +154,9 @@ func TestContainerPinMarshalSortedOutput(t *testing.T) { pin, ok := cache2.GetContainerPin("a-image:latest") require.True(t, ok) assert.Equal(t, "sha256:aaa", pin.Digest) + pin, ok = cache2.GetContainerPin("m-image:v2") + require.True(t, ok) + assert.Equal(t, "sha256:mmm", pin.Digest) pin, ok = cache2.GetContainerPin("z-image:latest") require.True(t, ok) assert.Equal(t, "sha256:zzz", pin.Digest)