diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b7e2b86566d..82e0cfa4a92 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -33,8 +33,8 @@ "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}, "ghcr.io/devcontainers/features/copilot-cli:latest": {}, "ghcr.io/devcontainers/features/docker-in-docker:2": {}, - "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/git-lfs:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/node:1": { "version": "24" } diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 8366ec146ee..80f6d339625 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -70,6 +70,11 @@ "version": "v5.6.0", "sha": "40f1582b2485089dde7abd97c1529aa768e1baff" }, + "actions/setup-go@v6": { + "repo": "actions/setup-go", + "version": "v6", + "sha": "4b73464bb391d4059bd26b0524d20df3927bd417" + }, "actions/setup-go@v6.2.0": { "repo": "actions/setup-go", "version": "v6.2.0", @@ -110,6 +115,11 @@ "version": "v6", "sha": "b7c566a772e6b6bfb58ed0dc250532a479d7789f" }, + "anchore/sbom-action@v0": { + "repo": "anchore/sbom-action", + "version": "v0", + "sha": "17ae1740179002c89186b61233e0f892c3118b11" + }, "anchore/sbom-action@v0.22.2": { "repo": "anchore/sbom-action", "version": "v0.22.2", diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 80bc1ca5179..d0824303a6a 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -296,7 +296,7 @@ jobs: name: Download CI workflow runs from last 7 days run: "# Download workflow runs for the ci workflow\ngh run list --repo ${{ github.repository }} --workflow=ci.yml --limit 100 --json databaseId,status,conclusion,createdAt,updatedAt,displayTitle,headBranch,event,url,workflowDatabaseId,number > /tmp/ci-runs.json\n\n# Create directory for artifacts\nmkdir -p /tmp/ci-artifacts\n\n# Download artifacts from recent runs (last 5 successful runs)\necho \"Downloading artifacts from recent CI runs...\"\ngh run list --repo ${{ github.repository }} --workflow=ci.yml --status success --limit 5 --json databaseId | jq -r '.[].databaseId' | while read -r run_id; do\n echo \"Processing run $run_id\"\n gh run download \"$run_id\" --repo ${{ github.repository }} --dir \"/tmp/ci-artifacts/$run_id\" 2>/dev/null || echo \"No artifacts for run $run_id\"\ndone\n\necho \"CI runs data saved to /tmp/ci-runs.json\"\necho \"Artifacts saved to /tmp/ci-artifacts/\"\n\n# Summarize downloaded artifacts\necho \"## Downloaded Artifacts\" >> \"$GITHUB_STEP_SUMMARY\"\nfind /tmp/ci-artifacts -type f -name \"*.txt\" -o -name \"*.html\" -o -name \"*.json\" | head -20 | while read -r f; do\n echo \"- $(basename \"$f\")\" >> \"$GITHUB_STEP_SUMMARY\"\ndone\n" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: cache: true go-version-file: go.mod diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 1fd126af52b..5e7b3fc0320 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -275,7 +275,7 @@ jobs: - name: Create gh-aw temp directory run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: cache: true go-version-file: go.mod diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 68daabb01c3..86eecf681d1 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -301,7 +301,7 @@ jobs: sudo apt-get update sudo apt-get install -y make - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: cache: true go-version-file: go.mod diff --git a/.github/workflows/release.lock.yml b/.github/workflows/release.lock.yml index 5feba38a6d8..b25f143c39a 100644 --- a/.github/workflows/release.lock.yml +++ b/.github/workflows/release.lock.yml @@ -1258,13 +1258,13 @@ jobs: - name: Download Go modules run: go mod download - name: Generate SBOM (SPDX format) - uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0 + uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0 with: artifact-name: sbom.spdx.json format: spdx-json output-file: sbom.spdx.json - name: Generate SBOM (CycloneDX format) - uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0 + uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0 with: artifact-name: sbom.cdx.json format: cyclonedx-json diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 7f790d39d0d..2da04725240 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -302,7 +302,7 @@ jobs: cache-dependency-path: actions/setup/js/package-lock.json node-version: "24" - name: Setup Go - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6 + uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6 with: cache: true go-version-file: go.mod diff --git a/pkg/cli/pr_command_test.go b/pkg/cli/pr_command_test.go index b221ec9b755..203772ddcfd 100644 --- a/pkg/cli/pr_command_test.go +++ b/pkg/cli/pr_command_test.go @@ -180,10 +180,4 @@ func TestNewPRTransferSubcommand(t *testing.T) { if repoFlag == nil { t.Error("Expected --repo flag to exist") } - - // Check that --verbose flag exists - verboseFlag := cmd.Flags().Lookup("verbose") - if verboseFlag == nil { - t.Error("Expected --verbose flag to exist") - } } diff --git a/pkg/workflow/action_pins_test.go b/pkg/workflow/action_pins_test.go index 91ef2797065..09844f3d405 100644 --- a/pkg/workflow/action_pins_test.go +++ b/pkg/workflow/action_pins_test.go @@ -297,9 +297,9 @@ func TestApplyActionPinToStep(t *testing.T) { func TestGetActionPinsSorting(t *testing.T) { pins := getActionPins() - // Verify we got all the pins (37 as of February 2026) - if len(pins) != 37 { - t.Errorf("getActionPins() returned %d pins, expected 37", len(pins)) + // Verify we got all the pins (39 as of February 2026) + if len(pins) != 39 { + t.Errorf("getActionPins() returned %d pins, expected 39", len(pins)) } // Verify they are sorted by version (descending) then by repository name (ascending) diff --git a/pkg/workflow/action_reference.go b/pkg/workflow/action_reference.go index b0cdd624a1f..99480db43c0 100644 --- a/pkg/workflow/action_reference.go +++ b/pkg/workflow/action_reference.go @@ -22,14 +22,14 @@ const ( // - actionMode: The action mode (dev or release) // - version: The version string to use for release mode // - actionTag: Optional override tag/SHA (takes precedence over version when in release mode) -// - data: Optional WorkflowData for SHA resolution (can be nil for standalone use) +// - resolver: Optional ActionSHAResolver for dynamic SHA resolution (can be nil for standalone use) // // Returns: // - For dev mode: "./actions/setup" (local path) -// - For release mode with data: "github/gh-aw/actions/setup@ # " (SHA-pinned) -// - For release mode without data: "github/gh-aw/actions/setup@" (tag-based, SHA resolved later) +// - For release mode with resolver: "github/gh-aw/actions/setup@ # " (SHA-pinned) +// - For release mode without resolver: "github/gh-aw/actions/setup@" (tag-based, SHA resolved later) // - Falls back to local path if version is invalid in release mode -func ResolveSetupActionReference(actionMode ActionMode, version string, actionTag string, data *WorkflowData) string { +func ResolveSetupActionReference(actionMode ActionMode, version string, actionTag string, resolver ActionSHAResolver) string { localPath := "./actions/setup" // Dev mode - return local path @@ -55,25 +55,23 @@ func ResolveSetupActionReference(actionMode ActionMode, version string, actionTa } // Construct the remote reference with tag: github/gh-aw/actions/setup@tag - remoteRef := fmt.Sprintf("%s/%s@%s", GitHubOrgRepo, actionPath, tag) - - // If WorkflowData is available, try to resolve the SHA - if data != nil { - actionRepo := fmt.Sprintf("%s/%s", GitHubOrgRepo, actionPath) - pinnedRef, err := GetActionPinWithData(actionRepo, tag, data) - if err != nil { - // In strict mode, GetActionPinWithData returns an error - actionRefLog.Printf("Failed to pin action %s@%s: %v", actionRepo, tag, err) - return "" - } - if pinnedRef != "" { - // Successfully resolved to SHA + actionRepo := fmt.Sprintf("%s/%s", GitHubOrgRepo, actionPath) + remoteRef := fmt.Sprintf("%s@%s", actionRepo, tag) + + // If a resolver is available, try to resolve the SHA + if resolver != nil { + sha, err := resolver.ResolveSHA(actionRepo, tag) + if err == nil && sha != "" { + pinnedRef := formatActionReference(actionRepo, sha, tag) actionRefLog.Printf("Release mode: resolved %s to SHA-pinned reference: %s", remoteRef, pinnedRef) return pinnedRef } + if err != nil { + actionRefLog.Printf("Failed to resolve SHA for %s@%s: %v", actionRepo, tag, err) + } } - // If WorkflowData is not available or SHA resolution failed, return tag-based reference + // If no resolver or SHA resolution failed, return tag-based reference // This is for backward compatibility with standalone workflow generators actionRefLog.Printf("Release mode: using tag-based remote action reference: %s (SHA will be resolved later)", remoteRef) return remoteRef @@ -104,11 +102,15 @@ func (c *Compiler) resolveActionReference(localActionPath string, data *Workflow // For ./actions/setup, check for compiler-level actionTag override first if localActionPath == "./actions/setup" { // Use compiler actionTag if available, otherwise check features + var resolver ActionSHAResolver + if data != nil && data.ActionResolver != nil { + resolver = data.ActionResolver + } if c.actionTag != "" { - return ResolveSetupActionReference(c.actionMode, c.version, c.actionTag, data) + return ResolveSetupActionReference(c.actionMode, c.version, c.actionTag, resolver) } if !hasActionTag { - return ResolveSetupActionReference(c.actionMode, c.version, "", data) + return ResolveSetupActionReference(c.actionMode, c.version, "", resolver) } } diff --git a/pkg/workflow/action_reference_test.go b/pkg/workflow/action_reference_test.go index 46c53b9798f..d503bb7de13 100644 --- a/pkg/workflow/action_reference_test.go +++ b/pkg/workflow/action_reference_test.go @@ -341,20 +341,14 @@ func TestResolveSetupActionReference(t *testing.T) { } func TestResolveSetupActionReferenceWithData(t *testing.T) { - t.Run("release mode with WorkflowData resolves SHA", func(t *testing.T) { + t.Run("release mode with resolver resolves SHA", func(t *testing.T) { // Create mock action resolver and cache cache := NewActionCache("") resolver := NewActionResolver(cache) - data := &WorkflowData{ - ActionResolver: resolver, - ActionCache: cache, - StrictMode: false, - } - // The resolver will fail to resolve github/gh-aw/actions/setup@v1.0.0 // since it's not a real tag, but it should fall back gracefully - ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", data) + ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", resolver) // Without a valid pin or successful resolution, should return tag-based reference expectedRef := "github/gh-aw/actions/setup@v1.0.0" @@ -363,7 +357,7 @@ func TestResolveSetupActionReferenceWithData(t *testing.T) { } }) - t.Run("release mode with nil data returns tag-based reference", func(t *testing.T) { + t.Run("release mode with nil resolver returns tag-based reference", func(t *testing.T) { ref := ResolveSetupActionReference(ActionModeRelease, "v1.0.0", "", nil) expectedRef := "github/gh-aw/actions/setup@v1.0.0" if ref != expectedRef { diff --git a/pkg/workflow/action_resolver.go b/pkg/workflow/action_resolver.go index 33522f5351e..aa0df9bd979 100644 --- a/pkg/workflow/action_resolver.go +++ b/pkg/workflow/action_resolver.go @@ -11,6 +11,11 @@ import ( var resolverLog = logger.New("workflow:action_resolver") +// ActionSHAResolver is the minimal interface for resolving an action tag to its commit SHA. +type ActionSHAResolver interface { + ResolveSHA(repo, version string) (string, error) +} + // ActionResolver handles resolving action SHAs using GitHub CLI type ActionResolver struct { cache *ActionCache diff --git a/pkg/workflow/data/action_pins.json b/pkg/workflow/data/action_pins.json index 8366ec146ee..80f6d339625 100644 --- a/pkg/workflow/data/action_pins.json +++ b/pkg/workflow/data/action_pins.json @@ -70,6 +70,11 @@ "version": "v5.6.0", "sha": "40f1582b2485089dde7abd97c1529aa768e1baff" }, + "actions/setup-go@v6": { + "repo": "actions/setup-go", + "version": "v6", + "sha": "4b73464bb391d4059bd26b0524d20df3927bd417" + }, "actions/setup-go@v6.2.0": { "repo": "actions/setup-go", "version": "v6.2.0", @@ -110,6 +115,11 @@ "version": "v6", "sha": "b7c566a772e6b6bfb58ed0dc250532a479d7789f" }, + "anchore/sbom-action@v0": { + "repo": "anchore/sbom-action", + "version": "v0", + "sha": "17ae1740179002c89186b61233e0f892c3118b11" + }, "anchore/sbom-action@v0.22.2": { "repo": "anchore/sbom-action", "version": "v0.22.2", diff --git a/pkg/workflow/maintenance_workflow.go b/pkg/workflow/maintenance_workflow.go index 674f7b7329e..d82bc7d17c8 100644 --- a/pkg/workflow/maintenance_workflow.go +++ b/pkg/workflow/maintenance_workflow.go @@ -149,9 +149,12 @@ jobs: `) // Get the setup action reference (local or remote based on mode) - // Pass nil for data since maintenance workflow doesn't have WorkflowData - // In release mode without data, it will return tag-based reference - setupActionRef := ResolveSetupActionReference(actionMode, version, actionTag, nil) + // Use the first available WorkflowData's ActionResolver to enable SHA pinning + var resolver ActionSHAResolver + if len(workflowDataList) > 0 && workflowDataList[0].ActionResolver != nil { + resolver = workflowDataList[0].ActionResolver + } + setupActionRef := ResolveSetupActionReference(actionMode, version, actionTag, resolver) // Add checkout step only in dev mode (for local action paths) if actionMode == ActionModeDev { diff --git a/pkg/workflow/maintenance_workflow_test.go b/pkg/workflow/maintenance_workflow_test.go index d72b3975582..cd8096726e2 100644 --- a/pkg/workflow/maintenance_workflow_test.go +++ b/pkg/workflow/maintenance_workflow_test.go @@ -287,6 +287,43 @@ func TestGenerateMaintenanceWorkflow_ActionTag(t *testing.T) { } }) + t.Run("release mode with action-tag and resolver uses SHA-pinned ref", func(t *testing.T) { + tmpDir := t.TempDir() + // Set up an action resolver with a cached SHA for the setup action + cache := NewActionCache(tmpDir) + cache.Set("github/gh-aw/actions/setup", "v0.47.4", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + resolver := NewActionResolver(cache) + + workflowDataListWithResolver := []*WorkflowData{ + { + Name: "test-workflow", + ActionResolver: resolver, + ActionPinWarnings: make(map[string]bool), + SafeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{ + Expires: 48, + }, + }, + }, + } + + err := GenerateMaintenanceWorkflow(workflowDataListWithResolver, tmpDir, "v1.0.0", ActionModeRelease, "v0.47.4", false) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + content, err := os.ReadFile(filepath.Join(tmpDir, "agentics-maintenance.yml")) + if err != nil { + t.Fatalf("Expected maintenance workflow to be generated: %v", err) + } + expectedRef := "github/gh-aw/actions/setup@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa # v0.47.4" + if !strings.Contains(string(content), expectedRef) { + t.Errorf("Expected SHA-pinned ref %q, got:\n%s", expectedRef, string(content)) + } + if strings.Contains(string(content), "uses: ./actions/setup") { + t.Errorf("Expected no local path in release mode with action-tag, got:\n%s", string(content)) + } + }) + t.Run("dev mode ignores action-tag and uses local path", func(t *testing.T) { tmpDir := t.TempDir() err := GenerateMaintenanceWorkflow(workflowDataList, tmpDir, "v1.0.0", ActionModeDev, "v0.47.4", false)