From aed9e17fa731ca7a10a6bed5ed0ad3d582749a9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 03:44:44 +0000 Subject: [PATCH 1/3] Initial plan From 8cc5d93b5762a97ef81d5ca02a98dadf4135b8db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 03:58:57 +0000 Subject: [PATCH 2/3] Remediate 14 custom-lint collection/perf findings Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/compile_pipeline.go | 11 +++++---- pkg/cli/mcp_tools_readonly.go | 17 ++++++------- pkg/cli/runner_guard_activation_gate.go | 16 ++++++------- pkg/cli/runner_guard_activation_gate_test.go | 8 +++---- .../globwalkignorederror.go | 11 +++++---- .../central_slash_command_workflow.go | 24 +++++++++---------- .../central_slash_command_workflow_test.go | 2 +- pkg/workflow/copilot_engine_execution.go | 8 ++++--- pkg/workflow/copilot_logs.go | 3 ++- pkg/workflow/engine_definition.go | 2 +- pkg/workflow/samples_replay.go | 2 +- pkg/workflow/samples_validation.go | 10 ++++---- 12 files changed, 61 insertions(+), 53 deletions(-) diff --git a/pkg/cli/compile_pipeline.go b/pkg/cli/compile_pipeline.go index e125dc604d9..ca2d562d715 100644 --- a/pkg/cli/compile_pipeline.go +++ b/pkg/cli/compile_pipeline.go @@ -22,12 +22,13 @@ package cli import ( + "cmp" "context" "errors" "fmt" "os" "path/filepath" - "sort" + "slices" "strings" "github.com/github/gh-aw/pkg/gitutil" @@ -684,11 +685,11 @@ func displayBatchCompilationNotices(compiler *workflow.Compiler, config CompileC count: count, }) } - sort.Slice(features, func(i, j int) bool { - if features[i].count != features[j].count { - return features[i].count > features[j].count + slices.SortFunc(features, func(a, b featureCount) int { + if a.count != b.count { + return cmp.Compare(b.count, a.count) } - return features[i].name < features[j].name + return cmp.Compare(a.name, b.name) }) fmt.Fprintln(os.Stderr, console.FormatWarningMessageStderr("Experimental features in use:")) diff --git a/pkg/cli/mcp_tools_readonly.go b/pkg/cli/mcp_tools_readonly.go index 412f4df01a9..e759b3fc573 100644 --- a/pkg/cli/mcp_tools_readonly.go +++ b/pkg/cli/mcp_tools_readonly.go @@ -533,13 +533,13 @@ func extractShellcheckDiagnostics(stderrOutput string) []string { lines := strings.Split(stderrOutput, "\n") diagnostics := make([]string, 0) - current := "" + var current strings.Builder flush := func() { - if strings.TrimSpace(current) != "" { - diagnostics = append(diagnostics, strings.TrimSpace(current)) + if text := strings.TrimSpace(current.String()); text != "" { + diagnostics = append(diagnostics, text) } - current = "" + current.Reset() } for _, line := range lines { @@ -547,10 +547,11 @@ func extractShellcheckDiagnostics(stderrOutput string) []string { switch { case strings.Contains(trimmed, "shellcheck findings in "): flush() - current = trimmed - case current != "" && (strings.Contains(trimmed, "script:") || strings.HasPrefix(trimmed, "script ")): - current += "\n" + trimmed - case current != "" && trimmed == "": + current.WriteString(trimmed) + case current.Len() > 0 && (strings.Contains(trimmed, "script:") || strings.HasPrefix(trimmed, "script ")): + current.WriteString("\n") + current.WriteString(trimmed) + case current.Len() > 0 && trimmed == "": flush() } } diff --git a/pkg/cli/runner_guard_activation_gate.go b/pkg/cli/runner_guard_activation_gate.go index e5c97106823..58fb4e19814 100644 --- a/pkg/cli/runner_guard_activation_gate.go +++ b/pkg/cli/runner_guard_activation_gate.go @@ -38,7 +38,7 @@ type runnerGuardWorkflow struct { // dependencies, has an if: condition referencing author_association. Workflows without such // a gate keep their findings. func filterRunnerGuardFindings(findings []runnerGuardFinding, gitRoot string) []runnerGuardFinding { - gatedJobsByFile := make(map[string]map[string]bool) + gatedJobsByFile := make(map[string]map[string]struct{}) filtered := make([]runnerGuardFinding, 0, len(findings)) for _, finding := range findings { @@ -53,7 +53,7 @@ func filterRunnerGuardFindings(findings []runnerGuardFinding, gitRoot string) [] gatedJobsByFile[finding.File] = gatedJobs } - if gatedJobs[finding.JobID] { + if _, isGated := gatedJobs[finding.JobID]; isGated { runnerGuardLog.Printf("Suppressing %s finding for gated job %q in %s", finding.RuleID, finding.JobID, finding.File) continue } @@ -107,8 +107,8 @@ func resolveRunnerGuardFilePath(gitRoot string, file string) string { // protected by an author_association check, either directly on the job's if: condition or // transitively through the needs: graph. An empty set is returned when the workflow cannot // be read or parsed, so that findings are preserved rather than silently dropped. -func authorAssociationGatedJobs(path string) map[string]bool { - gated := make(map[string]bool) +func authorAssociationGatedJobs(path string) map[string]struct{} { + gated := make(map[string]struct{}) if path == "" { return gated } @@ -132,11 +132,11 @@ func authorAssociationGatedJobs(path string) map[string]bool { for range len(workflow.Jobs) { changed := false for jobID, job := range workflow.Jobs { - if gated[jobID] { + if _, isGated := gated[jobID]; isGated { continue } if hasAuthorAssociationCheck(job.If) || anyJobGated(gated, jobNeeds(job.Needs)) { - gated[jobID] = true + gated[jobID] = struct{}{} changed = true } } @@ -154,9 +154,9 @@ func hasAuthorAssociationCheck(condition string) bool { } // anyJobGated reports whether any of the named jobs is in the gated set. -func anyJobGated(gated map[string]bool, needs []string) bool { +func anyJobGated(gated map[string]struct{}, needs []string) bool { for _, need := range needs { - if gated[need] { + if _, isGated := gated[need]; isGated { return true } } diff --git a/pkg/cli/runner_guard_activation_gate_test.go b/pkg/cli/runner_guard_activation_gate_test.go index 388dde2eb9a..89afd7dbf73 100644 --- a/pkg/cli/runner_guard_activation_gate_test.go +++ b/pkg/cli/runner_guard_activation_gate_test.go @@ -77,10 +77,10 @@ func TestAuthorAssociationGatedJobs(t *testing.T) { gated := authorAssociationGatedJobs(filepath.Join(gitRoot, ".github", "workflows", "gated.lock.yml")) - assert.True(t, gated["pre_activation"]) - assert.True(t, gated["activation"]) - assert.True(t, gated["agent"]) - assert.True(t, gated["conclusion"]) + assert.Contains(t, gated, "pre_activation") + assert.Contains(t, gated, "activation") + assert.Contains(t, gated, "agent") + assert.Contains(t, gated, "conclusion") }) t.Run("workflow without a gate has no gated jobs", func(t *testing.T) { diff --git a/pkg/linters/globwalkignorederror/globwalkignorederror.go b/pkg/linters/globwalkignorederror/globwalkignorederror.go index 64af819e426..956479855cf 100644 --- a/pkg/linters/globwalkignorederror/globwalkignorederror.go +++ b/pkg/linters/globwalkignorederror/globwalkignorederror.go @@ -19,9 +19,9 @@ var Analyzer = analyzerutil.New("globwalkignorederror", "reports filepath.Glob a // checkedFuncs maps package import path to the set of function names within // that package whose discarded error return should be flagged. -var checkedFuncs = map[string]map[string]bool{ - "path/filepath": {"Glob": true}, - "os": {"ReadDir": true}, +var checkedFuncs = map[string]map[string]struct{}{ + "path/filepath": {"Glob": {}}, + "os": {"ReadDir": {}}, } func run(pass *analysis.Pass) (any, error) { @@ -72,7 +72,10 @@ func analyzeGlobWalkAssign(pass *analysis.Pass, n ast.Node, generatedFiles filec return } funcs, ok := checkedFuncs[pkgName.Imported().Path()] - if !ok || !funcs[sel.Sel.Name] { + if !ok { + return + } + if _, checked := funcs[sel.Sel.Name]; !checked { return } position := pass.Fset.PositionFor(call.Pos(), false) diff --git a/pkg/workflow/central_slash_command_workflow.go b/pkg/workflow/central_slash_command_workflow.go index 57f05a836c5..25bb88ce1a9 100644 --- a/pkg/workflow/central_slash_command_workflow.go +++ b/pkg/workflow/central_slash_command_workflow.go @@ -106,7 +106,7 @@ func centralRoutingCommandNames(wd *WorkflowData) []string { return nil } -func collectCentralCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string][]slashCommandRoute, map[string]map[string]bool) { +func collectCentralCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string][]slashCommandRoute, map[string]map[string]struct{}) { slashRoutesByCommand, mergedEvents := collectCentralSlashCommandRoutes(workflowDataList) labelRoutesByCommand := collectCentralLabelCommandRoutes(workflowDataList, mergedEvents) return slashRoutesByCommand, labelRoutesByCommand, mergedEvents @@ -128,9 +128,9 @@ func removeIfExists(path string) error { return nil } -func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]bool) { +func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[string][]slashCommandRoute, map[string]map[string]struct{}) { routesByCommand := make(map[string][]slashCommandRoute) - mergedEvents := make(map[string]map[string]bool) + mergedEvents := make(map[string]map[string]struct{}) for _, wd := range workflowDataList { commandNames := centralRoutingCommandNames(wd) @@ -153,10 +153,10 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str // Merge workflow-level subscriptions using YAML-ready GitHub event names. for _, event := range MergeEventsForYAML(filteredEvents) { if mergedEvents[event.EventName] == nil { - mergedEvents[event.EventName] = make(map[string]bool) + mergedEvents[event.EventName] = make(map[string]struct{}) } for _, t := range event.Types { - mergedEvents[event.EventName][t] = true + mergedEvents[event.EventName][t] = struct{}{} } } @@ -200,7 +200,7 @@ func collectCentralSlashCommandRoutes(workflowDataList []*WorkflowData) (map[str return routesByCommand, mergedEvents } -func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEvents map[string]map[string]bool) map[string][]slashCommandRoute { +func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEvents map[string]map[string]struct{}) map[string][]slashCommandRoute { routesByLabel := make(map[string][]slashCommandRoute) for _, wd := range workflowDataList { @@ -223,9 +223,9 @@ func collectCentralLabelCommandRoutes(workflowDataList []*WorkflowData, mergedEv for _, eventName := range routeEvents { if mergedEvents[eventName] == nil { - mergedEvents[eventName] = make(map[string]bool) + mergedEvents[eventName] = make(map[string]struct{}) } - mergedEvents[eventName]["labeled"] = true + mergedEvents[eventName]["labeled"] = struct{}{} } for _, labelName := range wd.LabelCommand { @@ -342,7 +342,7 @@ func resolveCentralizedEventStatusComment(wd *WorkflowData, eventName string) bo func buildCentralSlashCommandWorkflowYAML( slashRoutesByCommand map[string][]slashCommandRoute, labelRoutesByCommand map[string][]slashCommandRoute, - mergedEvents map[string]map[string]bool, + mergedEvents map[string]map[string]struct{}, runsOn string, setupActionRef string, helpCommands []helpCommandEntry, @@ -592,7 +592,7 @@ func writeCentralRouteTypeSummary(b *strings.Builder, routesByTrigger map[string } } -func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[string]map[string]bool) { +func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[string]map[string]struct{}) { b.WriteString(` permissions: actions: write contents: read @@ -608,7 +608,7 @@ func writeCentralSlashRoutePermissions(b *strings.Builder, mergedEvents map[stri } } -func needsPullRequestsPermission(mergedEvents map[string]map[string]bool) bool { +func needsPullRequestsPermission(mergedEvents map[string]map[string]struct{}) bool { // issue_comment and issues events can target pull requests (issue-backed PR payloads), // and runtime branch resolution uses pulls.get for those cases. pullRequestEvents := []string{"issues", "issue_comment", "pull_request", "pull_request_comment", "pull_request_review_comment", "pull_request_review"} @@ -710,7 +710,7 @@ func formatRunsOnSnippetForInlineValue(runsOn string) string { return "\n" + strings.Join(lines, "\n") } -func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]bool) { +func writeCentralSlashEventsYAML(b *strings.Builder, mergedEvents map[string]map[string]struct{}) { eventOrder := []string{ "issues", "issue_comment", diff --git a/pkg/workflow/central_slash_command_workflow_test.go b/pkg/workflow/central_slash_command_workflow_test.go index b6dd78c6557..779144580f0 100644 --- a/pkg/workflow/central_slash_command_workflow_test.go +++ b/pkg/workflow/central_slash_command_workflow_test.go @@ -492,7 +492,7 @@ func TestBuildHelpCommandEntries_ReservedHelpCommandName(t *testing.T) { require.Equal(t, "help", entries[0].Command) } -func typeSetKeys(typeSet map[string]bool) []string { +func typeSetKeys(typeSet map[string]struct{}) []string { out := make([]string, 0, len(typeSet)) for key := range typeSet { out = append(out, key) diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index e96fe035f7b..e408b08943b 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -753,10 +753,12 @@ func buildEngineCommandScriptSetup(command string) string { // engine.command intentionally accepts shell-form commands from trusted workflow // configuration authored in-repo; preserve shell semantics and forward driver args. scriptContent := fmt.Sprintf("#!/usr/bin/env bash\nset +o histexpand\nset -eo pipefail\n%s \"$@\"\n", command) - heredocDelimiter := "GH_AW_ENGINE_COMMAND_EOF" - for strings.Contains(scriptContent, heredocDelimiter) { - heredocDelimiter += "_X" + var delimiter strings.Builder + delimiter.WriteString("GH_AW_ENGINE_COMMAND_EOF") + for strings.Contains(scriptContent, delimiter.String()) { + delimiter.WriteString("_X") } + heredocDelimiter := delimiter.String() return fmt.Sprintf(`mkdir -p /tmp/gh-aw GH_AW_PREV_UMASK="$(umask)" diff --git a/pkg/workflow/copilot_logs.go b/pkg/workflow/copilot_logs.go index 46abb18f36d..62b63d71b3b 100644 --- a/pkg/workflow/copilot_logs.go +++ b/pkg/workflow/copilot_logs.go @@ -256,7 +256,8 @@ func (p *copilotSessionJSONLParser) handleUserEntry(entry SessionEntry) { if !ok { continue } - if outputSize := len(content.Content); outputSize > 0 { + if content.Content != "" { + outputSize := len(content.Content) if toolInfo, exists := p.toolCallMap[toolName]; exists { if outputSize > toolInfo.MaxOutputSize { toolInfo.MaxOutputSize = outputSize diff --git a/pkg/workflow/engine_definition.go b/pkg/workflow/engine_definition.go index 1e78160b3e4..bf7c6c4331b 100644 --- a/pkg/workflow/engine_definition.go +++ b/pkg/workflow/engine_definition.go @@ -459,7 +459,7 @@ func loadKnownEngineImports(download func(context.Context) ([]byte, error)) map[ for _, engine := range catalog.Engines { id := strings.ToLower(strings.TrimSpace(engine.ID)) importPath := strings.TrimSpace(engine.Import) - if len(id) == 0 || len(importPath) == 0 { + if id == "" || importPath == "" { continue } loaded[id] = knownEngineImportWithCompilerRef(importPath) diff --git a/pkg/workflow/samples_replay.go b/pkg/workflow/samples_replay.go index fd7a3b62131..08883238295 100644 --- a/pkg/workflow/samples_replay.go +++ b/pkg/workflow/samples_replay.go @@ -42,7 +42,7 @@ func collectSampleEntries(config *SafeOutputsConfig) []SampleEntry { args := make(map[string]any, len(sample)) var sidecars map[string]any for k, v := range sample { - if sidecarKeys[k] { + if _, isSidecar := sidecarKeys[k]; isSidecar { if sidecars == nil { sidecars = make(map[string]any) } diff --git a/pkg/workflow/samples_validation.go b/pkg/workflow/samples_validation.go index 7bbcc300916..dec043589e5 100644 --- a/pkg/workflow/samples_validation.go +++ b/pkg/workflow/samples_validation.go @@ -29,12 +29,12 @@ const sampleRuntimeExpressionPlaceholder = "aw_sample" // that are NOT passed to the MCP tool's `tools/call` arguments. They are stripped // from the sample before schema validation and consumed by the replay driver // (e.g. to pre-stage a branch + patch on disk). -var sampleSidecarFields = map[string]map[string]bool{ +var sampleSidecarFields = map[string]map[string]struct{}{ "create_pull_request": { - "patch": true, + "patch": {}, }, "push_to_pull_request_branch": { - "patch": true, + "patch": {}, }, } @@ -409,10 +409,10 @@ func schemaNumberAsInt(schema map[string]any, key string) (int, bool) { // stripSidecarFields returns a shallow copy of sample with sidecar keys removed. // The original map is never modified, even when no sidecars are configured — // callers may mutate the returned map without affecting the caller's input. -func stripSidecarFields(sample map[string]any, sidecars map[string]bool) map[string]any { +func stripSidecarFields(sample map[string]any, sidecars map[string]struct{}) map[string]any { out := make(map[string]any, len(sample)) for k, v := range sample { - if sidecars[k] { + if _, isSidecar := sidecars[k]; isSidecar { continue } out[k] = v From 9d3a367ae6f4dac768aaf2821da520d25e040e71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 9 Aug 2026 11:27:29 +0000 Subject: [PATCH 3/3] Revert strings.Builder for heredoc delimiter to plain string concat with nolint Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/copilot_engine_execution.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/workflow/copilot_engine_execution.go b/pkg/workflow/copilot_engine_execution.go index e408b08943b..0b3420a181a 100644 --- a/pkg/workflow/copilot_engine_execution.go +++ b/pkg/workflow/copilot_engine_execution.go @@ -753,12 +753,10 @@ func buildEngineCommandScriptSetup(command string) string { // engine.command intentionally accepts shell-form commands from trusted workflow // configuration authored in-repo; preserve shell semantics and forward driver args. scriptContent := fmt.Sprintf("#!/usr/bin/env bash\nset +o histexpand\nset -eo pipefail\n%s \"$@\"\n", command) - var delimiter strings.Builder - delimiter.WriteString("GH_AW_ENGINE_COMMAND_EOF") - for strings.Contains(scriptContent, delimiter.String()) { - delimiter.WriteString("_X") + heredocDelimiter := "GH_AW_ENGINE_COMMAND_EOF" + for strings.Contains(scriptContent, heredocDelimiter) { //nolint:stringsconcatloop -- trivial cold path, runs 0 times in normal operation + heredocDelimiter += "_X" } - heredocDelimiter := delimiter.String() return fmt.Sprintf(`mkdir -p /tmp/gh-aw GH_AW_PREV_UMASK="$(umask)"