From 25aca6bb54235690876eb7d73a95c457a5af6fc0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:09:32 +0000 Subject: [PATCH 01/13] Initial plan From 1dd6f451c9ab0942706c4fd5195fe526c97b3049 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:22:30 +0000 Subject: [PATCH 02/13] feat: add lightweight lock-file-only gh aw lint command Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- cmd/gh-aw/main.go | 3 + docs/src/content/docs/setup/cli.md | 15 ++++ pkg/cli/actionlint.go | 34 ++++++++- pkg/cli/lint_command.go | 114 +++++++++++++++++++++++++++++ pkg/cli/lint_command_test.go | 60 +++++++++++++++ 5 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/lint_command.go create mode 100644 pkg/cli/lint_command_test.go diff --git a/cmd/gh-aw/main.go b/cmd/gh-aw/main.go index 9a5c6aa5c37..89f2c40f592 100644 --- a/cmd/gh-aw/main.go +++ b/cmd/gh-aw/main.go @@ -765,6 +765,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all projectCmd := cli.NewProjectCommand() checksCmd := cli.NewChecksCommand() validateCmd := cli.NewValidateCommand(validateEngine) + lintCmd := cli.NewLintCommand() domainsCmd := cli.NewDomainsCommand() experimentsCmd := cli.NewExperimentsCommand() @@ -782,6 +783,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all // Development Commands compileCmd.GroupID = "development" validateCmd.GroupID = "development" + lintCmd.GroupID = "development" mcpCmd.GroupID = "development" fixCmd.GroupID = "development" domainsCmd.GroupID = "development" @@ -836,6 +838,7 @@ Use "` + string(constants.CLIExtensionPrefix) + ` help all" to show help for all rootCmd.AddCommand(secretsCmd) rootCmd.AddCommand(fixCmd) rootCmd.AddCommand(validateCmd) + rootCmd.AddCommand(lintCmd) rootCmd.AddCommand(completionCmd) rootCmd.AddCommand(hashCmd) rootCmd.AddCommand(projectCmd) diff --git a/docs/src/content/docs/setup/cli.md b/docs/src/content/docs/setup/cli.md index e9526c7ce91..0c2bdaab336 100644 --- a/docs/src/content/docs/setup/cli.md +++ b/docs/src/content/docs/setup/cli.md @@ -296,6 +296,21 @@ gh aw validate --engine copilot # Override AI engine All linters (`zizmor`, `actionlint`, `poutine`), `--validate`, and `--no-emit` are always-on defaults and cannot be disabled. Accepts the same workflow ID format as `compile`. +#### `lint` + +Lint existing `.lock.yml` workflow files from disk with actionlint only. This command does not recompile Markdown workflows, and skips `zizmor`/`poutine`. + +```bash wrap +gh aw lint # Lint all .github/workflows/*.lock.yml +gh aw lint .github/workflows/foo.lock.yml # Lint a specific lock file +gh aw lint --dir .github/workflows # Lint all lock files in a directory +gh aw lint --shellcheck --pyflakes # Enable actionlint script integrations +``` + +**Options:** `--dir/-d`, `--shellcheck`, `--pyflakes` + +By default, shellcheck and pyflakes integrations are disabled to reduce noise for generated `run:` scripts. Built-in actionlint ignore patterns cover gh-aw-specific extensions such as `job.workflow_*` context properties and the `copilot-requests` permission scope. + ### Testing #### `trial` diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 92f69cc584b..428c14b8040 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -22,6 +22,12 @@ var actionlintLog = logger.New("cli:actionlint") // actionlintVersion caches the actionlint version to avoid repeated Docker calls var actionlintVersion string +type actionlintRunOptions struct { + IncludeShellcheck bool + IncludePyflakes bool + IgnorePatterns []string +} + // getActionlintDocsURL returns the documentation URL for a given actionlint error kind // Error kinds map to documentation anchors at https://github.com/rhysd/actionlint/blob/main/docs/checks.md func getActionlintDocsURL(kind string) string { @@ -187,6 +193,13 @@ func getActionlintVersion() (string, error) { // runActionlintOnFiles runs the actionlint linter on one or more .lock.yml files using Docker func runActionlintOnFiles(lockFiles []string, verbose bool, strict bool) error { + return runActionlintOnFilesWithOptions(lockFiles, verbose, strict, actionlintRunOptions{ + IncludeShellcheck: true, + IncludePyflakes: true, + }) +} + +func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bool, options actionlintRunOptions) error { if len(lockFiles) == 0 { return nil } @@ -236,15 +249,32 @@ func runActionlintOnFiles(lockFiles []string, verbose bool, strict bool) error { "rhysd/actionlint:latest", "-format", "{{json .}}", } + if !options.IncludeShellcheck { + dockerArgs = append(dockerArgs, "-shellcheck=") + } + if !options.IncludePyflakes { + dockerArgs = append(dockerArgs, "-pyflakes=") + } + for _, ignorePattern := range options.IgnorePatterns { + dockerArgs = append(dockerArgs, "-ignore", ignorePattern) + } dockerArgs = append(dockerArgs, relPaths...) cmd := exec.CommandContext(ctx, "docker", dockerArgs...) // Always show that actionlint is running (regular verbosity) + integrationStatus := "with shellcheck & pyflakes" + if !options.IncludeShellcheck && !options.IncludePyflakes { + integrationStatus = "without shellcheck/pyflakes" + } else if !options.IncludeShellcheck { + integrationStatus = "without shellcheck" + } else if !options.IncludePyflakes { + integrationStatus = "without pyflakes" + } if len(lockFiles) == 1 { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running actionlint (includes shellcheck & pyflakes) on "+relPaths[0])) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running actionlint ("+integrationStatus+") on "+relPaths[0])) } else { - fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (includes shellcheck & pyflakes) on %d files", len(lockFiles)))) + fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage(fmt.Sprintf("Running actionlint (%s) on %d files", integrationStatus, len(lockFiles)))) } // In verbose mode, also show the command that users can run directly diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go new file mode 100644 index 00000000000..4ad78bc5f00 --- /dev/null +++ b/pkg/cli/lint_command.go @@ -0,0 +1,114 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/github/gh-aw/pkg/constants" + "github.com/spf13/cobra" +) + +var defaultGHAWActionlintIgnorePatterns = []string{ + `unknown permission scope "copilot-requests"`, + `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, +} + +// NewLintCommand creates the lint command. +func NewLintCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "lint [lock-file-or-directory]...", + Short: "Lint existing .lock.yml workflows with actionlint only", + Long: `Lint existing .lock.yml workflow files from disk using actionlint only. + +This command does not recompile Markdown workflows and does not run zizmor or poutine. +By default, shellcheck and pyflakes integrations are disabled for generated run scripts. + +Examples: + ` + string(constants.CLIExtensionPrefix) + ` lint + ` + string(constants.CLIExtensionPrefix) + ` lint .github/workflows/foo.lock.yml + ` + string(constants.CLIExtensionPrefix) + ` lint --dir .github/workflows + ` + string(constants.CLIExtensionPrefix) + ` lint --shellcheck --pyflakes`, + RunE: func(cmd *cobra.Command, args []string) error { + workflowDir, _ := cmd.Flags().GetString("dir") + includeShellcheck, _ := cmd.Flags().GetBool("shellcheck") + includePyflakes, _ := cmd.Flags().GetBool("pyflakes") + verbose, _ := cmd.Flags().GetBool("verbose") + + lockFiles, err := resolveLockFilesForLint(args, workflowDir) + if err != nil { + return err + } + + initActionlintStats() + defer displayActionlintSummary() + + return runActionlintOnFilesWithOptions(lockFiles, verbose, true, actionlintRunOptions{ + IncludeShellcheck: includeShellcheck, + IncludePyflakes: includePyflakes, + IgnorePatterns: defaultGHAWActionlintIgnorePatterns, + }) + }, + } + + cmd.Flags().StringP("dir", "d", ".github/workflows", "Directory to scan for *.lock.yml files when no arguments are provided") + cmd.Flags().Bool("shellcheck", false, "Enable shellcheck integration in actionlint") + cmd.Flags().Bool("pyflakes", false, "Enable pyflakes integration in actionlint") + + RegisterDirFlagCompletion(cmd, "dir") + + return cmd +} + +func resolveLockFilesForLint(inputs []string, workflowDir string) ([]string, error) { + candidates := inputs + if len(candidates) == 0 { + candidates = []string{workflowDir} + } + + var lockFiles []string + for _, candidate := range candidates { + lockFilesFromCandidate, err := expandLintCandidate(candidate) + if err != nil { + return nil, err + } + lockFiles = append(lockFiles, lockFilesFromCandidate...) + } + + if len(lockFiles) == 0 { + return nil, fmt.Errorf("no .lock.yml files found to lint") + } + + slices.Sort(lockFiles) + lockFiles = slices.Compact(lockFiles) + + return lockFiles, nil +} + +func expandLintCandidate(candidate string) ([]string, error) { + absCandidate, err := filepath.Abs(candidate) + if err != nil { + return nil, fmt.Errorf("failed to resolve path %q: %w", candidate, err) + } + + info, err := os.Stat(absCandidate) + if err != nil { + return nil, fmt.Errorf("failed to access %q: %w", candidate, err) + } + + if info.IsDir() { + lockFiles, err := filepath.Glob(filepath.Join(absCandidate, "*.lock.yml")) + if err != nil { + return nil, fmt.Errorf("failed to scan %q for .lock.yml files: %w", candidate, err) + } + return lockFiles, nil + } + + if !strings.HasSuffix(absCandidate, ".lock.yml") { + return nil, fmt.Errorf("path %q is not a .lock.yml file or directory", candidate) + } + + return []string{absCandidate}, nil +} diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go new file mode 100644 index 00000000000..07c4ff62efd --- /dev/null +++ b/pkg/cli/lint_command_test.go @@ -0,0 +1,60 @@ +//go:build !integration + +package cli + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewLintCommand(t *testing.T) { + cmd := NewLintCommand() + + require.NotNil(t, cmd, "NewLintCommand should return a non-nil command") + assert.Equal(t, "lint", cmd.Name(), "Command name should be 'lint'") + require.NotNil(t, cmd.Flags().Lookup("dir"), "lint command should have a --dir flag") + assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir should have -d shorthand") + require.NotNil(t, cmd.Flags().Lookup("shellcheck"), "lint command should have a --shellcheck flag") + require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag") + assert.Contains(t, defaultGHAWActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, + "lint command should include built-in ignore for gh-aw permission extension") +} + +func TestResolveLockFilesForLint(t *testing.T) { + tempDir := t.TempDir() + lockA := filepath.Join(tempDir, "a.lock.yml") + lockB := filepath.Join(tempDir, "b.lock.yml") + nonLock := filepath.Join(tempDir, "workflow.md") + + require.NoError(t, os.WriteFile(lockA, []byte("name: a"), 0o600), "should create lock file a") + require.NoError(t, os.WriteFile(lockB, []byte("name: b"), 0o600), "should create lock file b") + require.NoError(t, os.WriteFile(nonLock, []byte("---"), 0o600), "should create non-lock file") + + t.Run("defaults to scanning dir when no args", func(t *testing.T) { + files, err := resolveLockFilesForLint(nil, tempDir) + require.NoError(t, err, "should resolve lock files from default dir") + assert.Equal(t, []string{lockA, lockB}, files, "should return sorted .lock.yml files only") + }) + + t.Run("accepts explicit lock file path", func(t *testing.T) { + files, err := resolveLockFilesForLint([]string{lockB}, tempDir) + require.NoError(t, err, "should accept explicit lock file") + assert.Equal(t, []string{lockB}, files, "should include explicit lock file") + }) + + t.Run("accepts explicit directory path", func(t *testing.T) { + files, err := resolveLockFilesForLint([]string{tempDir}, tempDir) + require.NoError(t, err, "should accept explicit directory") + assert.Equal(t, []string{lockA, lockB}, files, "should expand directory to lock files") + }) + + t.Run("rejects non lock file path", func(t *testing.T) { + _, err := resolveLockFilesForLint([]string{nonLock}, tempDir) + require.Error(t, err, "should reject non-lock file path") + assert.Contains(t, err.Error(), "is not a .lock.yml file or directory", "error should explain allowed path types") + }) +} From a87a13854b0a9db60dda99ef58c90f106fcbaef9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:24:57 +0000 Subject: [PATCH 03/13] chore: polish lint command naming and messaging consistency Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 2 +- pkg/cli/lint_command.go | 4 ++-- pkg/cli/lint_command_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 428c14b8040..e6f3b9e09dc 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -263,7 +263,7 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo cmd := exec.CommandContext(ctx, "docker", dockerArgs...) // Always show that actionlint is running (regular verbosity) - integrationStatus := "with shellcheck & pyflakes" + integrationStatus := "with shellcheck/pyflakes" if !options.IncludeShellcheck && !options.IncludePyflakes { integrationStatus = "without shellcheck/pyflakes" } else if !options.IncludeShellcheck { diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 4ad78bc5f00..04b0a97afae 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -var defaultGHAWActionlintIgnorePatterns = []string{ +var defaultGhAwActionlintIgnorePatterns = []string{ `unknown permission scope "copilot-requests"`, `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, } @@ -48,7 +48,7 @@ Examples: return runActionlintOnFilesWithOptions(lockFiles, verbose, true, actionlintRunOptions{ IncludeShellcheck: includeShellcheck, IncludePyflakes: includePyflakes, - IgnorePatterns: defaultGHAWActionlintIgnorePatterns, + IgnorePatterns: defaultGhAwActionlintIgnorePatterns, }) }, } diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go index 07c4ff62efd..7c28d4cbfec 100644 --- a/pkg/cli/lint_command_test.go +++ b/pkg/cli/lint_command_test.go @@ -20,7 +20,7 @@ func TestNewLintCommand(t *testing.T) { assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir should have -d shorthand") require.NotNil(t, cmd.Flags().Lookup("shellcheck"), "lint command should have a --shellcheck flag") require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag") - assert.Contains(t, defaultGHAWActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, + assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, "lint command should include built-in ignore for gh-aw permission extension") } From b24ef2d776c40885765ab0983315b072dafaaa00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:27:33 +0000 Subject: [PATCH 04/13] test: cover lint actionlint option messaging helper Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 22 ++++++++++++------- pkg/cli/actionlint_test.go | 41 ++++++++++++++++++++++++++++++++++++ pkg/cli/lint_command.go | 4 ++-- pkg/cli/lint_command_test.go | 2 +- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index e6f3b9e09dc..a7708414e53 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -28,6 +28,19 @@ type actionlintRunOptions struct { IgnorePatterns []string } +func buildActionlintIntegrationStatus(includeShellcheck bool, includePyflakes bool) string { + switch { + case includeShellcheck && includePyflakes: + return "with shellcheck/pyflakes" + case includeShellcheck: + return "without pyflakes" + case includePyflakes: + return "without shellcheck" + default: + return "without shellcheck/pyflakes" + } +} + // getActionlintDocsURL returns the documentation URL for a given actionlint error kind // Error kinds map to documentation anchors at https://github.com/rhysd/actionlint/blob/main/docs/checks.md func getActionlintDocsURL(kind string) string { @@ -263,14 +276,7 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo cmd := exec.CommandContext(ctx, "docker", dockerArgs...) // Always show that actionlint is running (regular verbosity) - integrationStatus := "with shellcheck/pyflakes" - if !options.IncludeShellcheck && !options.IncludePyflakes { - integrationStatus = "without shellcheck/pyflakes" - } else if !options.IncludeShellcheck { - integrationStatus = "without shellcheck" - } else if !options.IncludePyflakes { - integrationStatus = "without pyflakes" - } + integrationStatus := buildActionlintIntegrationStatus(options.IncludeShellcheck, options.IncludePyflakes) if len(lockFiles) == 1 { fmt.Fprintf(os.Stderr, "%s\n", console.FormatInfoMessage("Running actionlint ("+integrationStatus+") on "+relPaths[0])) } else { diff --git a/pkg/cli/actionlint_test.go b/pkg/cli/actionlint_test.go index 89a197d2be9..c473cc2264e 100644 --- a/pkg/cli/actionlint_test.go +++ b/pkg/cli/actionlint_test.go @@ -324,3 +324,44 @@ func TestGetActionlintDocsURL(t *testing.T) { }) } } + +func TestBuildActionlintIntegrationStatus(t *testing.T) { + tests := []struct { + name string + includeShellcheck bool + includePyflakes bool + expected string + }{ + { + name: "both integrations enabled", + includeShellcheck: true, + includePyflakes: true, + expected: "with shellcheck/pyflakes", + }, + { + name: "only shellcheck enabled", + includeShellcheck: true, + includePyflakes: false, + expected: "without pyflakes", + }, + { + name: "only pyflakes enabled", + includeShellcheck: false, + includePyflakes: true, + expected: "without shellcheck", + }, + { + name: "both integrations disabled", + includeShellcheck: false, + includePyflakes: false, + expected: "without shellcheck/pyflakes", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := buildActionlintIntegrationStatus(tt.includeShellcheck, tt.includePyflakes) + assert.Equal(t, tt.expected, result, "integration status should match") + }) + } +} diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 04b0a97afae..4ad78bc5f00 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -var defaultGhAwActionlintIgnorePatterns = []string{ +var defaultGHAWActionlintIgnorePatterns = []string{ `unknown permission scope "copilot-requests"`, `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, } @@ -48,7 +48,7 @@ Examples: return runActionlintOnFilesWithOptions(lockFiles, verbose, true, actionlintRunOptions{ IncludeShellcheck: includeShellcheck, IncludePyflakes: includePyflakes, - IgnorePatterns: defaultGhAwActionlintIgnorePatterns, + IgnorePatterns: defaultGHAWActionlintIgnorePatterns, }) }, } diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go index 7c28d4cbfec..07c4ff62efd 100644 --- a/pkg/cli/lint_command_test.go +++ b/pkg/cli/lint_command_test.go @@ -20,7 +20,7 @@ func TestNewLintCommand(t *testing.T) { assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir should have -d shorthand") require.NotNil(t, cmd.Flags().Lookup("shellcheck"), "lint command should have a --shellcheck flag") require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag") - assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, + assert.Contains(t, defaultGHAWActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, "lint command should include built-in ignore for gh-aw permission extension") } From ff67e41a4618ee566b1294704de806a351bcb211 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:31:00 +0000 Subject: [PATCH 05/13] docs: clarify actionlint option structs and lint ignore naming Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 7 ++++++- pkg/cli/lint_command.go | 4 ++-- pkg/cli/lint_command_test.go | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index a7708414e53..748303c4855 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -22,12 +22,17 @@ var actionlintLog = logger.New("cli:actionlint") // actionlintVersion caches the actionlint version to avoid repeated Docker calls var actionlintVersion string +// actionlintRunOptions configures optional actionlint integrations and ignores. type actionlintRunOptions struct { IncludeShellcheck bool IncludePyflakes bool - IgnorePatterns []string + // IgnorePatterns contains regular expressions passed to actionlint via + // repeated -ignore flags to suppress known false positives. + IgnorePatterns []string } +// buildActionlintIntegrationStatus returns a human-readable description of the +// shellcheck/pyflakes integration state for actionlint execution messages. func buildActionlintIntegrationStatus(includeShellcheck bool, includePyflakes bool) string { switch { case includeShellcheck && includePyflakes: diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 4ad78bc5f00..04b0a97afae 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -var defaultGHAWActionlintIgnorePatterns = []string{ +var defaultGhAwActionlintIgnorePatterns = []string{ `unknown permission scope "copilot-requests"`, `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, } @@ -48,7 +48,7 @@ Examples: return runActionlintOnFilesWithOptions(lockFiles, verbose, true, actionlintRunOptions{ IncludeShellcheck: includeShellcheck, IncludePyflakes: includePyflakes, - IgnorePatterns: defaultGHAWActionlintIgnorePatterns, + IgnorePatterns: defaultGhAwActionlintIgnorePatterns, }) }, } diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go index 07c4ff62efd..7c28d4cbfec 100644 --- a/pkg/cli/lint_command_test.go +++ b/pkg/cli/lint_command_test.go @@ -20,7 +20,7 @@ func TestNewLintCommand(t *testing.T) { assert.Equal(t, "d", cmd.Flags().Lookup("dir").Shorthand, "--dir should have -d shorthand") require.NotNil(t, cmd.Flags().Lookup("shellcheck"), "lint command should have a --shellcheck flag") require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag") - assert.Contains(t, defaultGHAWActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, + assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, "lint command should include built-in ignore for gh-aw permission extension") } From 6518f3b7d47f147293218dd5d17fdef3db986acb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:33:07 +0000 Subject: [PATCH 06/13] chore: document actionlint integration disable flags Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 748303c4855..4d4a60f895c 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -268,9 +268,11 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo "-format", "{{json .}}", } if !options.IncludeShellcheck { + // Empty value disables shellcheck integration in actionlint. dockerArgs = append(dockerArgs, "-shellcheck=") } if !options.IncludePyflakes { + // Empty value disables pyflakes integration in actionlint. dockerArgs = append(dockerArgs, "-pyflakes=") } for _, ignorePattern := range options.IgnorePatterns { From 5590c3148ad2a7ea720f00d4157eb08de02e8b1b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:35:35 +0000 Subject: [PATCH 07/13] chore: clarify lint strict mode and test file permissions Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/lint_command.go | 4 +++- pkg/cli/lint_command_test.go | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 04b0a97afae..2ec72fcd1e7 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -45,7 +45,9 @@ Examples: initActionlintStats() defer displayActionlintSummary() - return runActionlintOnFilesWithOptions(lockFiles, verbose, true, actionlintRunOptions{ + // Lint should fail on any actionlint error. + strictActionlint := true + return runActionlintOnFilesWithOptions(lockFiles, verbose, strictActionlint, actionlintRunOptions{ IncludeShellcheck: includeShellcheck, IncludePyflakes: includePyflakes, IgnorePatterns: defaultGhAwActionlintIgnorePatterns, diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go index 7c28d4cbfec..788b7ef4c75 100644 --- a/pkg/cli/lint_command_test.go +++ b/pkg/cli/lint_command_test.go @@ -30,9 +30,9 @@ func TestResolveLockFilesForLint(t *testing.T) { lockB := filepath.Join(tempDir, "b.lock.yml") nonLock := filepath.Join(tempDir, "workflow.md") - require.NoError(t, os.WriteFile(lockA, []byte("name: a"), 0o600), "should create lock file a") - require.NoError(t, os.WriteFile(lockB, []byte("name: b"), 0o600), "should create lock file b") - require.NoError(t, os.WriteFile(nonLock, []byte("---"), 0o600), "should create non-lock file") + require.NoError(t, os.WriteFile(lockA, []byte("name: a"), 0o644), "should create lock file a") + require.NoError(t, os.WriteFile(lockB, []byte("name: b"), 0o644), "should create lock file b") + require.NoError(t, os.WriteFile(nonLock, []byte("---"), 0o644), "should create non-lock file") t.Run("defaults to scanning dir when no args", func(t *testing.T) { files, err := resolveLockFilesForLint(nil, tempDir) From c3d579f05b24304d86315f4d23c65094fd711493 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 03:38:44 +0000 Subject: [PATCH 08/13] test: assert all default gh-aw actionlint ignore patterns Agent-Logs-Url: https://github.com/github/gh-aw/sessions/24535202-772c-48b3-9cff-bd34ad3e1806 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/lint_command.go | 2 ++ pkg/cli/lint_command_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index 2ec72fcd1e7..aeb930be3ae 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -12,7 +12,9 @@ import ( ) var defaultGhAwActionlintIgnorePatterns = []string{ + // gh-aw extends GitHub Actions permissions with copilot-requests. `unknown permission scope "copilot-requests"`, + // gh-aw exposes additional job.workflow_* context properties. `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, } diff --git a/pkg/cli/lint_command_test.go b/pkg/cli/lint_command_test.go index 788b7ef4c75..8e48630c326 100644 --- a/pkg/cli/lint_command_test.go +++ b/pkg/cli/lint_command_test.go @@ -22,6 +22,8 @@ func TestNewLintCommand(t *testing.T) { require.NotNil(t, cmd.Flags().Lookup("pyflakes"), "lint command should have a --pyflakes flag") assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `unknown permission scope "copilot-requests"`, "lint command should include built-in ignore for gh-aw permission extension") + assert.Contains(t, defaultGhAwActionlintIgnorePatterns, `property "workflow_(repository|sha|ref|file_path)" is not defined in object type`, + "lint command should include built-in ignore for gh-aw workflow context extensions") } func TestResolveLockFilesForLint(t *testing.T) { From f89db3f96b6d0c5e3a8f2a0ace5e4909ba763da7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 04:10:54 +0000 Subject: [PATCH 09/13] fix: clarify partial actionlint integration status messages Agent-Logs-Url: https://github.com/github/gh-aw/sessions/01d9ac32-b5da-428e-85de-82ff4ae4f07a Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 4 ++-- pkg/cli/actionlint_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 4d4a60f895c..b2156b32038 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -38,9 +38,9 @@ func buildActionlintIntegrationStatus(includeShellcheck bool, includePyflakes bo case includeShellcheck && includePyflakes: return "with shellcheck/pyflakes" case includeShellcheck: - return "without pyflakes" + return "with shellcheck only" case includePyflakes: - return "without shellcheck" + return "with pyflakes only" default: return "without shellcheck/pyflakes" } diff --git a/pkg/cli/actionlint_test.go b/pkg/cli/actionlint_test.go index c473cc2264e..d5c738a6fea 100644 --- a/pkg/cli/actionlint_test.go +++ b/pkg/cli/actionlint_test.go @@ -342,13 +342,13 @@ func TestBuildActionlintIntegrationStatus(t *testing.T) { name: "only shellcheck enabled", includeShellcheck: true, includePyflakes: false, - expected: "without pyflakes", + expected: "with shellcheck only", }, { name: "only pyflakes enabled", includeShellcheck: false, includePyflakes: true, - expected: "without shellcheck", + expected: "with pyflakes only", }, { name: "both integrations disabled", From ee3ce09c089fe76ffa030945868649b4c126ac8d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:40:17 +0000 Subject: [PATCH 10/13] feat: add lock-lint make target and context-aware actionlint runs Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c0f99d2b-4f32-4fb1-9e62-6e3dd9a47b42 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- Makefile | 13 +++++++++++ pkg/cli/actionlint.go | 36 ++++++++++++++++++++----------- pkg/cli/actionlint_test.go | 3 ++- pkg/cli/compile_external_tools.go | 5 ++++- pkg/cli/compile_validation.go | 5 +++-- pkg/cli/lint_command.go | 2 +- 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/Makefile b/Makefile index c734b053ab1..19a3050ba74 100644 --- a/Makefile +++ b/Makefile @@ -525,6 +525,12 @@ actionlint: build @echo "Validating workflows with actionlint..." ./$(BINARY_NAME) compile --actionlint +# Run lock-file-only lint using gh aw lint +.PHONY: lint-lock +lint-lock: build + @echo "Linting committed lock files with gh aw lint..." + ./$(BINARY_NAME) lint + # Format code .PHONY: fmt fmt: fmt-go fmt-cjs fmt-json @@ -758,6 +764,11 @@ agent-finish: deps-dev fmt lint build build-wasm test-all fix recompile dependab agent-report-progress: build fmt test-unit @echo "Pre-PR validation passed. Safe to call report_progress." +# Extended pre-PR gate with lock-file-only linting. +.PHONY: agent-report-progress-lint +agent-report-progress-lint: agent-report-progress lint-lock + @echo "Pre-PR validation + lock-file lint passed. Safe to call report_progress." + # Help target .PHONY: help help: @@ -813,6 +824,7 @@ help: @echo " security-gosec - Run gosec Go security scanner" @echo " security-govulncheck - Run govulncheck for known vulnerabilities" @echo " actionlint - Validate workflows with actionlint (depends on build)" + @echo " lint-lock - Run lock-file-only lint with gh aw lint (depends on build)" @echo " validate-workflows - Validate compiled workflow lock files (depends on build)" @echo " install - Install binary locally" @echo " sync-action-pins - Sync actions-lock.json from .github/aw to pkg/actionpins/data and pkg/workflow/data (runs automatically during build)" @@ -832,5 +844,6 @@ help: @echo " agent-finish - Complete validation sequence (build, test, fix, recompile, fmt, lint, security-scan)" @echo " agent-report-progress - Lightweight pre-PR gate: build + fmt + test-unit (<30s)" + @echo " agent-report-progress-lint - Pre-PR gate + gh aw lint lock-file check" @echo " sbom - Generate SBOM in SPDX and CycloneDX formats (requires syft)" @echo " help - Show this help message" diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index b2156b32038..b4589b98b3a 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -168,21 +168,26 @@ func displayActionlintSummary() { fmt.Fprintf(os.Stderr, "\n%s\n", separator) } -// getActionlintVersion fetches and caches the actionlint version from Docker -func getActionlintVersion() (string, error) { +// getActionlintVersion fetches and caches the actionlint version from Docker. +// The provided context allows caller-driven cancellation. +func getActionlintVersion(ctx context.Context) (string, error) { // Return cached version if already fetched if actionlintVersion != "" { return actionlintVersion, nil } + if ctx == nil { + ctx = context.Background() + } + actionlintLog.Print("Fetching actionlint version from Docker") // Run docker command to get version with a 30 second timeout - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + versionCtx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() cmd := exec.CommandContext( - ctx, + versionCtx, "docker", "run", "--rm", @@ -209,24 +214,28 @@ func getActionlintVersion() (string, error) { return version, nil } -// runActionlintOnFiles runs the actionlint linter on one or more .lock.yml files using Docker -func runActionlintOnFiles(lockFiles []string, verbose bool, strict bool) error { - return runActionlintOnFilesWithOptions(lockFiles, verbose, strict, actionlintRunOptions{ +// runActionlintOnFiles runs the actionlint linter on one or more .lock.yml files using Docker. +// The provided context allows caller-driven cancellation. +func runActionlintOnFiles(ctx context.Context, lockFiles []string, verbose bool, strict bool) error { + return runActionlintOnFilesWithOptions(ctx, lockFiles, verbose, strict, actionlintRunOptions{ IncludeShellcheck: true, IncludePyflakes: true, }) } -func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bool, options actionlintRunOptions) error { +func runActionlintOnFilesWithOptions(ctx context.Context, lockFiles []string, verbose bool, strict bool, options actionlintRunOptions) error { if len(lockFiles) == 0 { return nil } + if ctx == nil { + ctx = context.Background() + } actionlintLog.Printf("Running actionlint on %d file(s): %v (verbose=%t, strict=%t)", len(lockFiles), lockFiles, verbose, strict) // Display actionlint version on first use if actionlintVersion == "" { - version, err := getActionlintVersion() + version, err := getActionlintVersion(ctx) if err != nil { // Log error but continue - version display is not critical actionlintLog.Printf("Could not fetch actionlint version: %v", err) @@ -255,7 +264,7 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo // docker run --rm -v "$(pwd)":/workdir -w /workdir rhysd/actionlint:latest -format '{{json .}}' ... // Adjust timeout based on number of files (1 minute per file, minimum 5 minutes) timeoutDuration := time.Duration(max(5, len(lockFiles))) * time.Minute - ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration) + runCtx, cancel := context.WithTimeout(ctx, timeoutDuration) defer cancel() // Build Docker command arguments @@ -280,7 +289,7 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo } dockerArgs = append(dockerArgs, relPaths...) - cmd := exec.CommandContext(ctx, "docker", dockerArgs...) + cmd := exec.CommandContext(runCtx, "docker", dockerArgs...) // Always show that actionlint is running (regular verbosity) integrationStatus := buildActionlintIntegrationStatus(options.IncludeShellcheck, options.IncludePyflakes) @@ -306,7 +315,7 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo err = cmd.Run() // Check for timeout - if ctx.Err() == context.DeadlineExceeded { + if runCtx.Err() == context.DeadlineExceeded { fileList := "files" if len(lockFiles) == 1 { fileList = filepath.Base(lockFiles[0]) @@ -316,6 +325,9 @@ func runActionlintOnFilesWithOptions(lockFiles []string, verbose bool, strict bo } return fmt.Errorf("actionlint timed out after %d minutes on %s - this may indicate a Docker or network issue", int(timeoutDuration.Minutes()), fileList) } + if runCtx.Err() == context.Canceled { + return fmt.Errorf("actionlint canceled: %w", runCtx.Err()) + } // Track workflows in statistics (count number of files validated) if actionlintStats != nil { diff --git a/pkg/cli/actionlint_test.go b/pkg/cli/actionlint_test.go index d5c738a6fea..a5936887c5e 100644 --- a/pkg/cli/actionlint_test.go +++ b/pkg/cli/actionlint_test.go @@ -3,6 +3,7 @@ package cli import ( + "context" "testing" "github.com/github/gh-aw/pkg/testutil" @@ -125,7 +126,7 @@ func TestGetActionlintVersion(t *testing.T) { defer func() { actionlintVersion = original }() actionlintVersion = "1.7.9" - version, err := getActionlintVersion() + version, err := getActionlintVersion(context.Background()) require.NoError(t, err, "should not error when version is cached") assert.Equal(t, "1.7.9", version, "should return cached version") } diff --git a/pkg/cli/compile_external_tools.go b/pkg/cli/compile_external_tools.go index 92ed71914b1..04f295bf5a6 100644 --- a/pkg/cli/compile_external_tools.go +++ b/pkg/cli/compile_external_tools.go @@ -22,6 +22,7 @@ package cli import ( + "context" "fmt" "os" @@ -34,7 +35,9 @@ var compileExternalToolsLog = logger.New("cli:compile_external_tools") // RunActionlintOnFiles runs actionlint on multiple lock files in a single batch. // This is more efficient than running actionlint once per file. func RunActionlintOnFiles(lockFiles []string, verbose bool, strict bool) error { - return runBatchLockFileTool("actionlint", lockFiles, verbose, strict, runActionlintOnFiles) + return runBatchLockFileTool("actionlint", lockFiles, verbose, strict, func(files []string, runVerbose bool, runStrict bool) error { + return runActionlintOnFiles(context.Background(), files, runVerbose, runStrict) + }) } // RunZizmorOnFiles runs zizmor on multiple lock files in a single batch. diff --git a/pkg/cli/compile_validation.go b/pkg/cli/compile_validation.go index 4148001188c..4d8e1ab9c09 100644 --- a/pkg/cli/compile_validation.go +++ b/pkg/cli/compile_validation.go @@ -1,6 +1,7 @@ package cli import ( + "context" "errors" "fmt" "os" @@ -90,7 +91,7 @@ func CompileWorkflowWithValidation(compiler *workflow.Compiler, filePath string, // Run actionlint on the generated lock file if requested // Note: For batch processing, use RunActionlintOnFiles instead if runActionlintPerFile { - if err := runActionlintOnFiles([]string{lockFile}, verbose, strict); err != nil { + if err := runActionlintOnFiles(context.Background(), []string{lockFile}, verbose, strict); err != nil { return fmt.Errorf("actionlint linter failed: %w", err) } } @@ -158,7 +159,7 @@ func CompileWorkflowDataWithValidation(compiler *workflow.Compiler, workflowData // Run actionlint on the generated lock file if requested // Note: For batch processing, use RunActionlintOnFiles instead if runActionlintPerFile { - if err := runActionlintOnFiles([]string{lockFile}, verbose, strict); err != nil { + if err := runActionlintOnFiles(context.Background(), []string{lockFile}, verbose, strict); err != nil { return fmt.Errorf("actionlint linter failed: %w", err) } } diff --git a/pkg/cli/lint_command.go b/pkg/cli/lint_command.go index aeb930be3ae..c71ebe4adcc 100644 --- a/pkg/cli/lint_command.go +++ b/pkg/cli/lint_command.go @@ -49,7 +49,7 @@ Examples: // Lint should fail on any actionlint error. strictActionlint := true - return runActionlintOnFilesWithOptions(lockFiles, verbose, strictActionlint, actionlintRunOptions{ + return runActionlintOnFilesWithOptions(cmd.Context(), lockFiles, verbose, strictActionlint, actionlintRunOptions{ IncludeShellcheck: includeShellcheck, IncludePyflakes: includePyflakes, IgnorePatterns: defaultGhAwActionlintIgnorePatterns, From 00faff718c160b3feb6704f81d0ce4e872ed3dd9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:43:18 +0000 Subject: [PATCH 11/13] refactor: require non-nil context in actionlint runners Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c0f99d2b-4f32-4fb1-9e62-6e3dd9a47b42 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index b4589b98b3a..245cba272e6 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -176,10 +176,6 @@ func getActionlintVersion(ctx context.Context) (string, error) { return actionlintVersion, nil } - if ctx == nil { - ctx = context.Background() - } - actionlintLog.Print("Fetching actionlint version from Docker") // Run docker command to get version with a 30 second timeout @@ -227,10 +223,6 @@ func runActionlintOnFilesWithOptions(ctx context.Context, lockFiles []string, ve if len(lockFiles) == 0 { return nil } - if ctx == nil { - ctx = context.Background() - } - actionlintLog.Printf("Running actionlint on %d file(s): %v (verbose=%t, strict=%t)", len(lockFiles), lockFiles, verbose, strict) // Display actionlint version on first use From 978c3423b31a616eeb2b67fa8998b1ef9d394066 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:47:34 +0000 Subject: [PATCH 12/13] chore: clarify actionlint cancellation error message Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c0f99d2b-4f32-4fb1-9e62-6e3dd9a47b42 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 245cba272e6..7f8fc437dca 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -318,7 +318,7 @@ func runActionlintOnFilesWithOptions(ctx context.Context, lockFiles []string, ve return fmt.Errorf("actionlint timed out after %d minutes on %s - this may indicate a Docker or network issue", int(timeoutDuration.Minutes()), fileList) } if runCtx.Err() == context.Canceled { - return fmt.Errorf("actionlint canceled: %w", runCtx.Err()) + return fmt.Errorf("actionlint was canceled before completion") } // Track workflows in statistics (count number of files validated) From c7b55cab48c9d0bc51944c08501d6120c37f801f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 06:50:10 +0000 Subject: [PATCH 13/13] chore: improve actionlint cancellation guidance Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c0f99d2b-4f32-4fb1-9e62-6e3dd9a47b42 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/actionlint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cli/actionlint.go b/pkg/cli/actionlint.go index 7f8fc437dca..c6c973abebe 100644 --- a/pkg/cli/actionlint.go +++ b/pkg/cli/actionlint.go @@ -318,7 +318,7 @@ func runActionlintOnFilesWithOptions(ctx context.Context, lockFiles []string, ve return fmt.Errorf("actionlint timed out after %d minutes on %s - this may indicate a Docker or network issue", int(timeoutDuration.Minutes()), fileList) } if runCtx.Err() == context.Canceled { - return fmt.Errorf("actionlint was canceled before completion") + return fmt.Errorf("actionlint was canceled before completion (for example by Ctrl+C or caller cancellation)") } // Track workflows in statistics (count number of files validated)