{"type":"tool.execution_complete","timestamp":"2026-08-11T17:57:26.675Z","data":{"toolName":"bash","mcpServerName":"","success":true,"result":{"content":"// Package errstringmatch implements a Go analysis linter that flags calls to\n// strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare on\n// err.Error() with a string literal — all perform brittle substring matching on\n// error messages instead of using errors.Is or errors.As.\npackage errstringmatch\n\nimport (\n\t\"go/ast\"\n\t\"go/types\"\n\n\t\"github.com/github/gh-aw/pkg/linters/internal/analyzerutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n\t\"golang.org/x/tools/go/analysis\"\n)\n\n// Analyzer is the err-string-match analysis pass.\nvar Analyzer = analyzerutil.New(\"errstringmatch\", \"reports strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare(err.Error(), \\\"...\\\") calls that perform brittle substring matching on error messages\", run)\n\n// brittleErrStringFuncs is the set of strings package functions that perform\n// brittle error-message matching when their first argument is err.Error().\nvar brittleErrStringFuncs = map[string]bool{\n\t\"Contains\": true,\n\t\"HasPrefix\": true,\n\t\"HasSuffix\": true,\n\t\"EqualFold\": true,\n\t\"Index\": true,\n\t\"LastIndex\": true,\n\t\"Compare\": true,\n}\n\nfunc run(pass *analysis.Pass) (any, error) {\n\tnoLintIndex, err := nolint.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgeneratedFiles, err := filecheck.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodeFilter := []ast.Node{\n\t\t(*ast.CallExpr)(nil),\n\t}\n\n\treturn analyzerutil.Preorder(pass, nodeFilter, func(n ast.Node) {\n\t\touter, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tposition := pass.Fset.PositionFor(outer.Pos(), false)\n\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n\t\t\treturn\n\t\t}\n\n\t\t// Match strings.<BrittleFunc>(X, Y)\n\t\tfuncName, matched := brittleErrStringFuncName(pass, outer)\n\t\tif !matched {\n\t\t\treturn\n\t\t}\n\t\tif len(outer.Args) != 2 {\n\t\t\treturn\n\t\t}\n\n\t\t// First arg must be a call to err.Error()\n\t\tif !isErrDotError(pass, outer.Args[0]) {\n\t\t\treturn\n\t\t}\n\n\t\t// Second arg must be a string literal (or at least a string type)\n\t\tif !isStringLiteral(pass, outer.Args[1]) {\n\t\t\treturn\n\t\t}\n\t\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"errstringmatch\") {\n\t\t\treturn\n\t\t}\n\n\t\tpass.ReportRangef(outer, \"avoid strings.%s(err.Error(), ...) — use errors.Is, errors.As, or a sentinel error instead\", funcName)\n\t})\n}\n\n// brittleErrStringFuncName returns the matched strings function name and true\n// when call is a strings.<BrittleFunc>(...) call expression.\nfunc brittleErrStringFuncName(pass *analysis.Pass, call *ast.CallExpr) (string, bool) {\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\tif !astutil.IsPkgSelector(pass, sel, \"strings\") {\n\t\treturn \"\", false\n\t}\n\tif brittleErrStringFuncs[sel.Sel.Name] {\n\t\treturn sel.Sel.Name, true\n\t}\n\treturn \"\", false\n}\n\n// isErrDotError returns true when expr is a method call of the form <expr>.Error()\n// where the receiver implements the error interface.\nfunc isErrDotError(pass *analysis.Pass, expr ast.Expr) bool {\n\tcall, ok := expr.(*ast.CallExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tif sel.Sel.Name != \"Error\" {\n\t\treturn false\n\t}\n\tif len(call.Args) != 0 {\n\t\treturn false\n\t}\n\t// Check that the receiver implements the error interface.\n\tt := pass.TypesInfo.TypeOf(sel.X)\n\tif t == nil {\n\t\treturn false\n\t}\n\treturn nolint.ImplementsError(t)\n}\n\n// isStringLiteral returns true when expr is a string literal or untyped string constant.\nfunc isStringLiteral(pass *analysis.Pass, expr ast.Expr) bool {\n\tif astutil.IsStringLiteral(expr) {\n\t\treturn true\n\t}\n\t// Also accept typed/untyped string constants (e.g. a const identifier).\n\tt := pass.TypesInfo.TypeOf(expr)\n\tif t == nil {\n\t\treturn false\n\t}\n\tbasic, ok := t.Underlying().(*types.Basic)\n\treturn ok && basic.Kind() == types.String\n}\n<shellId: 84 completed with exit code 0>","detailedContent":"// Package errstringmatch implements a Go analysis linter that flags calls to\n// strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare on\n// err.Error() with a string literal — all perform brittle substring matching on\n// error messages instead of using errors.Is or errors.As.\npackage errstringmatch\n\nimport (\n\t\"go/ast\"\n\t\"go/types\"\n\n\t\"github.com/github/gh-aw/pkg/linters/internal/analyzerutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n\t\"golang.org/x/tools/go/analysis\"\n)\n\n// Analyzer is the err-string-match analysis pass.\nvar Analyzer = analyzerutil.New(\"errstringmatch\", \"reports strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare(err.Error(), \\\"...\\\") calls that perform brittle substring matching on error messages\", run)\n\n// brittleErrStringFuncs is the set of strings package functions that perform\n// brittle error-message matching when their first argument is err.Error().\nvar brittleErrStringFuncs = map[string]bool{\n\t\"Contains\": true,\n\t\"HasPrefix\": true,\n\t\"HasSuffix\": true,\n\t\"EqualFold\": true,\n\t\"Index\": true,\n\t\"LastIndex\": true,\n\t\"Compare\": true,\n}\n\nfunc run(pass *analysis.Pass) (any, error) {\n\tnoLintIndex, err := nolint.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgeneratedFiles, err := filecheck.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodeFilter := []ast.Node{\n\t\t(*ast.CallExpr)(nil),\n\t}\n\n\treturn analyzerutil.Preorder(pass, nodeFilter, func(n ast.Node) {\n\t\touter, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tposition := pass.Fset.PositionFor(outer.Pos(), false)\n\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n\t\t\treturn\n\t\t}\n\n\t\t// Match strings.<BrittleFunc>(X, Y)\n\t\tfuncName, matched := brittleErrStringFuncName(pass, outer)\n\t\tif !matched {\n\t\t\treturn\n\t\t}\n\t\tif len(outer.Args) != 2 {\n\t\t\treturn\n\t\t}\n\n\t\t// First arg must be a call to err.Error()\n\t\tif !isErrDotError(pass, outer.Args[0]) {\n\t\t\treturn\n\t\t}\n\n\t\t// Second arg must be a string literal (or at least a string type)\n\t\tif !isStringLiteral(pass, outer.Args[1]) {\n\t\t\treturn\n\t\t}\n\t\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"errstringmatch\") {\n\t\t\treturn\n\t\t}\n\n\t\tpass.ReportRangef(outer, \"avoid strings.%s(err.Error(), ...) — use errors.Is, errors.As, or a sentinel error instead\", funcName)\n\t})\n}\n\n// brittleErrStringFuncName returns the matched strings function name and true\n// when call is a strings.<BrittleFunc>(...) call expression.\nfunc brittleErrStringFuncName(pass *analysis.Pass, call *ast.CallExpr) (string, bool) {\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\tif !astutil.IsPkgSelector(pass, sel, \"strings\") {\n\t\treturn \"\", false\n\t}\n\tif brittleErrStringFuncs[sel.Sel.Name] {\n\t\treturn sel.Sel.Name, true\n\t}\n\treturn \"\", false\n}\n\n// isErrDotError returns true when expr is a method call of the form <expr>.Error()\n// where the receiver implements the error interface.\nfunc isErrDotError(pass *analysis.Pass, expr ast.Expr) bool {\n\tcall, ok := expr.(*ast.CallExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tif sel.Sel.Name != \"Error\" {\n\t\treturn false\n\t}\n\tif len(call.Args) != 0 {\n\t\treturn false\n\t}\n\t// Check that the receiver implements the error interface.\n\tt := pass.TypesInfo.TypeOf(sel.X)\n\tif t == nil {\n\t\treturn false\n\t}\n\treturn nolint.ImplementsError(t)\n}\n\n// isStringLiteral returns true when expr is a string literal or untyped string constant.\nfunc isStringLiteral(pass *analysis.Pass, expr ast.Expr) bool {\n\tif astutil.IsStringLiteral(expr) {\n\t\treturn true\n\t}\n\t// Also accept typed/untyped string constants (e.g. a const identifier).\n\tt := pass.TypesInfo.TypeOf(expr)\n\tif t == nil {\n\t\treturn false\n\t}\n\tbasic, ok := t.Underlying().(*types.Basic)\n\treturn ok && basic.Kind() == types.String\n}\n<shellId: 84 completed with exit code 0>","contents":[{"type":"shell_exit","shellId":"84","exitCode":0,"outputTruncated":false,"cwd":"/home/runner/work/gh-aw/gh-aw","outputPreview":"// Package errstringmatch implements a Go analysis linter that flags calls to\n// strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare on\n// err.Error() with a string literal — all perform brittle substring matching on\n// error messages instead of using errors.Is or errors.As.\npackage errstringmatch\n\nimport (\n\t\"go/ast\"\n\t\"go/types\"\n\n\t\"github.com/github/gh-aw/pkg/linters/internal/analyzerutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/astutil\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/filecheck\"\n\t\"github.com/github/gh-aw/pkg/linters/internal/nolint\"\n\t\"golang.org/x/tools/go/analysis\"\n)\n\n// Analyzer is the err-string-match analysis pass.\nvar Analyzer = analyzerutil.New(\"errstringmatch\", \"reports strings.Contains/HasPrefix/HasSuffix/EqualFold/Index/LastIndex/Compare(err.Error(), \\\"...\\\") calls that perform brittle substring matching on error messages\", run)\n\n// brittleErrStringFuncs is the set of strings package functions that perform\n// brittle error-message matching when their first argument is err.Error().\nvar brittleErrStringFuncs = map[string]bool{\n\t\"Contains\": true,\n\t\"HasPrefix\": true,\n\t\"HasSuffix\": true,\n\t\"EqualFold\": true,\n\t\"Index\": true,\n\t\"LastIndex\": true,\n\t\"Compare\": true,\n}\n\nfunc run(pass *analysis.Pass) (any, error) {\n\tnoLintIndex, err := nolint.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tgeneratedFiles, err := filecheck.Index(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodeFilter := []ast.Node{\n\t\t(*ast.CallExpr)(nil),\n\t}\n\n\treturn analyzerutil.Preorder(pass, nodeFilter, func(n ast.Node) {\n\t\touter, ok := n.(*ast.CallExpr)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tposition := pass.Fset.PositionFor(outer.Pos(), false)\n\t\tif filecheck.ShouldSkipFilename(position.Filename, generatedFiles) {\n\t\t\treturn\n\t\t}\n\n\t\t// Match strings.<BrittleFunc>(X, Y)\n\t\tfuncName, matched := brittleErrStringFuncName(pass, outer)\n\t\tif !matched {\n\t\t\treturn\n\t\t}\n\t\tif len(outer.Args) != 2 {\n\t\t\treturn\n\t\t}\n\n\t\t// First arg must be a call to err.Error()\n\t\tif !isErrDotError(pass, outer.Args[0]) {\n\t\t\treturn\n\t\t}\n\n\t\t// Second arg must be a string literal (or at least a string type)\n\t\tif !isStringLiteral(pass, outer.Args[1]) {\n\t\t\treturn\n\t\t}\n\t\tif nolint.HasDirectiveForLinter(position, noLintIndex, \"errstringmatch\") {\n\t\t\treturn\n\t\t}\n\n\t\tpass.ReportRangef(outer, \"avoid strings.%s(err.Error(), ...) — use errors.Is, errors.As, or a sentinel error instead\", funcName)\n\t})\n}\n\n// brittleErrStringFuncName returns the matched strings function name and true\n// when call is a strings.<BrittleFunc>(...) call expression.\nfunc brittleErrStringFuncName(pass *analysis.Pass, call *ast.CallExpr) (string, bool) {\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn \"\", false\n\t}\n\tif !astutil.IsPkgSelector(pass, sel, \"strings\") {\n\t\treturn \"\", false\n\t}\n\tif brittleErrStringFuncs[sel.Sel.Name] {\n\t\treturn sel.Sel.Name, true\n\t}\n\treturn \"\", false\n}\n\n// isErrDotError returns true when expr is a method call of the form <expr>.Error()\n// where the receiver implements the error interface.\nfunc isErrDotError(pass *analysis.Pass, expr ast.Expr) bool {\n\tcall, ok := expr.(*ast.CallExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tsel, ok := call.Fun.(*ast.SelectorExpr)\n\tif !ok {\n\t\treturn false\n\t}\n\tif sel.Sel.Name != \"Error\" {\n\t\treturn false\n\t}\n\tif len(call.Args) != 0 {\n\t\treturn false\n\t}\n\t// Check that the receiver implements the error interface.\n\tt := pass.TypesInfo.TypeOf(sel.X)\n\tif t == nil {\n\t\treturn false\n\t}\n\treturn nolint.ImplementsError(t)\n}\n\n// isStringLiteral returns true when expr is a string literal or untyped string constant.\nfunc isStringLiteral(pass *analysis.Pass, expr ast.Expr) bool {\n\tif astutil.IsStringLiteral(expr) {\n\t\treturn true\n\t}\n\t// Also accept typed/untyped string constants (e.g. a const identifier).\n\tt := pass.TypesInfo.TypeOf(expr)\n\tif t == nil {\n\t\treturn false\n\t}\n\tbasic, ok := t.Underlying().(*types.Basic)\n\treturn ok && basic.Kind() == types.String\n}\n"}]}}}
{"type":"assistant.message","timestamp":"2026-08-11T17:57:58.605Z","data":{"content":"Now I have enough context. Let's launch the linter-writer sub-agent."}}
{"type":"tool.execution_start","timestamp":"2026-08-11T17:57:58.607Z","data":{"toolName":"task","mcpServerName":""}}
{"type":"subagent.started","timestamp":"2026-08-11T17:57:58.618Z","data":{"agentName":"general-purpose","agentDisplayName":"General Purpose Agent","toolCallId":"toolu_014hFutmZfpieQbdcgXv5zhq"}}
[copilot-sdk-driver] [sdk-driver] error: Execution failed: Error: No model available. Check policy enablement under GitHub Settings > Copilot
{"type":"subagent.completed","timestamp":"2026-08-11T17:57:58.680Z","data":{"agentName":"general-purpose","toolCallId":"toolu_014hFutmZfpieQbdcgXv5zhq"}}
{"type":"tool.execution_complete","timestamp":"2026-08-11T17:57:58.716Z","data":{"toolName":"task","mcpServerName":"","success":false}}
{"type":"subagent.completed","timestamp":"2026-08-11T17:57:58.724Z","data":{"agentName":"research","toolCallId":"toolu_01VPvuS5SFsndrQTMDkmveHA"}}
{"type":"subagent.completed","timestamp":"2026-08-11T17:57:58.725Z","data":{"agentName":"explore","toolCallId":"toolu_01SLWSPamTkXwnN1EYahzKmM"}}
{"type":"subagent.completed","timestamp":"2026-08-11T17:57:58.726Z","data":{"agentName":"general-purpose","toolCallId":"toolu_014hFutmZfpieQbdcgXv5zhq"}}
Workflow Failure
Workflow: Linter Miner
Branch: main
Run: https://github.com/github/gh-aw/actions/runs/31518785706
Warning
Engine Failure: The
copilotengine terminated unexpectedly.Last agent output:
Action Required
Assign this issue to an agent to debug and fix the issue.
Debug with any coding agent
Use this prompt with any coding agent (GitHub Copilot, Claude, Gemini, etc.):
Manually invoke the agent
Debug this workflow failure using your favorite Agent CLI and the
agentic-workflowsprompt.agentic-workflowsskill from.github/skills/agentic-workflows/SKILL.mdor https://github.com/github/gh-aw/blob/main/.github/skills/agentic-workflows/SKILL.mddebug the agentic workflow linter-miner failure in https://github.com/github/gh-aw/actions/runs/31518785706Tip
Stop reporting this workflow as a failure
To stop a workflow from creating failure issues, set
report-failure-as-issue: falsein its frontmatter: