From 44c23427bfc869eab50720005ac535db647a6239 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 03:57:16 +0000 Subject: [PATCH 1/6] Initial plan From 7be06b67c52a6e000a1eb569d2bc58f3677367b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 04:10:20 +0000 Subject: [PATCH 2/6] Fix unchecked assertions and manifest parameter limit Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/cli/jsonworkflow_to_markdown.go | 14 +++- pkg/cli/update_manifest.go | 71 ++++++++++++------- pkg/cli/workflows.go | 13 ++-- pkg/linters/ctxbackground/ctxbackground.go | 6 +- pkg/linters/errormessage/errormessage.go | 6 +- pkg/linters/errstringmatch/errstringmatch.go | 6 +- .../excessivefuncparams.go | 11 ++- .../fileclosenotdeferred.go | 6 +- .../fprintlnsprintf/fprintlnsprintf.go | 6 +- pkg/linters/largefunc/largefunc.go | 6 +- .../manualmutexunlock/manualmutexunlock.go | 6 +- .../osexitinlibrary/osexitinlibrary.go | 11 ++- .../ossetenvlibrary/ossetenvlibrary.go | 11 ++- .../panic-in-library-code.go | 11 ++- pkg/linters/rawloginlib/rawloginlib.go | 11 ++- .../regexpcompileinfunction.go | 6 +- pkg/parser/virtual_fs.go | 16 ++++- pkg/workflow/dispatch_repository.go | 4 +- pkg/workflow/mcp_scripts_generator.go | 6 +- .../repository_features_validation.go | 12 +++- pkg/workflow/safe_outputs_actions.go | 6 +- .../safe_outputs_config_generation.go | 6 +- pkg/workflow/safe_outputs_workflow_helpers.go | 4 +- pkg/workflow/yaml.go | 8 ++- 24 files changed, 201 insertions(+), 62 deletions(-) diff --git a/pkg/cli/jsonworkflow_to_markdown.go b/pkg/cli/jsonworkflow_to_markdown.go index 8d54d8ebec0..65b9a1be996 100644 --- a/pkg/cli/jsonworkflow_to_markdown.go +++ b/pkg/cli/jsonworkflow_to_markdown.go @@ -445,14 +445,24 @@ func convertTriggersToOn(t *JSONWorkflowTriggers) (any, []string) { // Single interval trigger → use the string shorthand directly. intervalType, hasInterval := parts["_interval"] if hasInterval && len(parts) == 1 { - return intervalType.(string), warnings + interval, ok := intervalType.(string) + if !ok { + warnings = append(warnings, "triggers.interval must resolve to a string value; skipped") + return nil, warnings + } + return interval, warnings } // Multiple triggers or non-interval: build a map. // Convert interval shorthand to a schedule cron entry. if hasInterval { delete(parts, "_interval") - parts["schedule"] = []any{map[string]any{"cron": intervalToFuzzySchedule(intervalType.(string))}} + interval, ok := intervalType.(string) + if !ok { + warnings = append(warnings, "triggers.interval must resolve to a string value; schedule trigger skipped") + } else { + parts["schedule"] = []any{map[string]any{"cron": intervalToFuzzySchedule(interval)}} + } } return parts, warnings diff --git a/pkg/cli/update_manifest.go b/pkg/cli/update_manifest.go index 0f411230756..18c2357c5f5 100644 --- a/pkg/cli/update_manifest.go +++ b/pkg/cli/update_manifest.go @@ -15,6 +15,16 @@ import ( var updateManifestLog = logger.New("cli:update_manifest") +type manifestManagedWorkflowUpdate struct { + wf *workflowWithSource + repo string + currentPath string + latestPath string + currentRef string + latestRef string + manifestSource string +} + func parseManifestSourceSpec(source string) (*RepoSpec, bool, error) { repoSpec, ok, err := parseRepositoryPackageSpec(strings.TrimSpace(source)) if !ok { @@ -136,7 +146,16 @@ func updateManifestWorkflowGroup(ctx context.Context, source string, grouped []* if oldPath == "" { oldPath = latestPath } - if err := updateManifestManagedWorkflow(ctx, wf, repoSpec.RepoSlug, oldPath, latestPath, currentRef, latestRef, manifestSource, opts); err != nil { + update := manifestManagedWorkflowUpdate{ + wf: wf, + repo: repoSpec.RepoSlug, + currentPath: oldPath, + latestPath: latestPath, + currentRef: currentRef, + latestRef: latestRef, + manifestSource: manifestSource, + } + if err := updateManifestManagedWorkflow(ctx, update, opts); err != nil { failures = append(failures, updateFailure{Name: wf.Name, Error: err.Error()}) continue } @@ -171,20 +190,20 @@ func removeManifestManagedWorkflow(workflowPath string) error { return nil } -func updateManifestManagedWorkflow(ctx context.Context, wf *workflowWithSource, repo, currentPath, latestPath, currentRef, latestRef, manifestSource string, opts UpdateWorkflowsOptions) error { - updateManifestLog.Printf("Updating manifest-managed workflow %s: %s@%s -> %s@%s", wf.Name, currentPath, currentRef, latestPath, latestRef) - sourceSpecCurrent := sourceSpecWithRef(&SourceSpec{Repo: repo, Path: currentPath}, currentRef) - newContent, err := downloadWorkflowContentFn(ctx, repo, latestPath, latestRef, opts.Verbose) +func updateManifestManagedWorkflow(ctx context.Context, update manifestManagedWorkflowUpdate, opts UpdateWorkflowsOptions) error { + updateManifestLog.Printf("Updating manifest-managed workflow %s: %s@%s -> %s@%s", update.wf.Name, update.currentPath, update.currentRef, update.latestPath, update.latestRef) + sourceSpecCurrent := sourceSpecWithRef(&SourceSpec{Repo: update.repo, Path: update.currentPath}, update.currentRef) + newContent, err := downloadWorkflowContentFn(ctx, update.repo, update.latestPath, update.latestRef, opts.Verbose) if err != nil { - return fmt.Errorf("failed to download workflow %s/%s@%s: %w", repo, latestPath, latestRef, err) + return fmt.Errorf("failed to download workflow %s/%s@%s: %w", update.repo, update.latestPath, update.latestRef, err) } - if !opts.Force && currentRef == latestRef && currentPath == latestPath { - sourceContent, err := downloadWorkflowContentFn(ctx, repo, currentPath, currentRef, opts.Verbose) + if !opts.Force && update.currentRef == update.latestRef && update.currentPath == update.latestPath { + sourceContent, err := downloadWorkflowContentFn(ctx, update.repo, update.currentPath, update.currentRef, opts.Verbose) if err == nil { - currentContent, readErr := os.ReadFile(wf.Path) - if readErr == nil && !hasLocalModifications(string(sourceContent), string(currentContent), sourceSpecCurrent, filepath.Dir(wf.Path), opts.Verbose) { - fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow %s is already up to date (%s)", wf.Name, shortRef(currentRef)))) + currentContent, readErr := os.ReadFile(update.wf.Path) + if readErr == nil && !hasLocalModifications(string(sourceContent), string(currentContent), sourceSpecCurrent, filepath.Dir(update.wf.Path), opts.Verbose) { + fmt.Fprintln(os.Stderr, console.FormatInfoMessage(fmt.Sprintf("Workflow %s is already up to date (%s)", update.wf.Name, shortRef(update.currentRef)))) return nil } } @@ -194,17 +213,17 @@ func updateManifestManagedWorkflow(ctx context.Context, wf *workflowWithSource, var finalContent string var hasConflicts bool if merge { - baseContent, err := downloadWorkflowContentFn(ctx, repo, currentPath, currentRef, opts.Verbose) + baseContent, err := downloadWorkflowContentFn(ctx, update.repo, update.currentPath, update.currentRef, opts.Verbose) if err != nil { - updateManifestLog.Printf("Cannot fetch base for 3-way merge of %s, falling back to overwrite: %v", wf.Name, err) + updateManifestLog.Printf("Cannot fetch base for 3-way merge of %s, falling back to overwrite: %v", update.wf.Name, err) merge = false } else { - currentContent, err := os.ReadFile(wf.Path) + currentContent, err := os.ReadFile(update.wf.Path) if err != nil { return fmt.Errorf("failed to read current workflow: %w", err) } - newSourceSpec := sourceSpecWithRef(&SourceSpec{Repo: repo, Path: latestPath}, latestRef) - mergedContent, conflicts, mergeErr := MergeWorkflowContent(string(baseContent), string(currentContent), string(newContent), sourceSpecCurrent, newSourceSpec, wf.Path, opts.Verbose) + newSourceSpec := sourceSpecWithRef(&SourceSpec{Repo: update.repo, Path: update.latestPath}, update.latestRef) + mergedContent, conflicts, mergeErr := MergeWorkflowContent(string(baseContent), string(currentContent), string(newContent), sourceSpecCurrent, newSourceSpec, update.wf.Path, opts.Verbose) if mergeErr != nil { return fmt.Errorf("failed to merge workflow content: %w", mergeErr) } @@ -216,17 +235,17 @@ func updateManifestManagedWorkflow(ctx context.Context, wf *workflowWithSource, finalContent = string(newContent) processedContent, err := processIncludesInContent(finalContent, &WorkflowSpec{ RepoSpec: RepoSpec{ - RepoSlug: repo, - Version: latestRef, + RepoSlug: update.repo, + Version: update.latestRef, }, - WorkflowPath: latestPath, - }, latestRef, filepath.Dir(wf.Path), opts.Verbose) + WorkflowPath: update.latestPath, + }, update.latestRef, filepath.Dir(update.wf.Path), opts.Verbose) if err == nil { finalContent = processedContent } } - finalContent, err = UpdateFieldInFrontmatter(finalContent, "source", manifestSource) + finalContent, err = UpdateFieldInFrontmatter(finalContent, "source", update.manifestSource) if err != nil { return fmt.Errorf("failed to update source frontmatter: %w", err) } @@ -245,20 +264,20 @@ func updateManifestManagedWorkflow(ctx context.Context, wf *workflowWithSource, if !opts.DisableSecurityScanner { if findings := workflow.ScanMarkdownSecurity(finalContent); len(findings) > 0 { - return fmt.Errorf("workflow '%s' failed security scan: %d issue(s) detected", wf.Name, len(findings)) + return fmt.Errorf("workflow '%s' failed security scan: %d issue(s) detected", update.wf.Name, len(findings)) } } - if err := os.WriteFile(wf.Path, []byte(finalContent), constants.FilePermPublic); err != nil { + if err := os.WriteFile(update.wf.Path, []byte(finalContent), constants.FilePermPublic); err != nil { return fmt.Errorf("failed to write updated workflow: %w", err) } if hasConflicts { - fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Updated %s from %s to %s with CONFLICTS - please review and resolve manually", wf.Name, shortRef(currentRef), shortRef(latestRef)))) + fmt.Fprintln(os.Stderr, console.FormatWarningMessage(fmt.Sprintf("Updated %s from %s to %s with CONFLICTS - please review and resolve manually", update.wf.Name, shortRef(update.currentRef), shortRef(update.latestRef)))) return nil } - fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Updated %s from %s to %s", wf.Name, shortRef(currentRef), shortRef(latestRef)))) + fmt.Fprintln(os.Stderr, console.FormatSuccessMessage(fmt.Sprintf("Updated %s from %s to %s", update.wf.Name, shortRef(update.currentRef), shortRef(update.latestRef)))) if !opts.NoCompile { - if err := compileWorkflowWithRefresh(ctx, wf.Path, opts.Verbose, false, opts.EngineOverride, true); err != nil { + if err := compileWorkflowWithRefresh(ctx, update.wf.Path, opts.Verbose, false, opts.EngineOverride, true); err != nil { return fmt.Errorf("failed to compile updated workflow: %w", err) } } diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index efff5ce03f9..74808f6677b 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -354,7 +354,12 @@ func fastParseTitleFromReader(r io.Reader) (string, error) { scanner := bufio.NewScanner(r) // Reuse the small initial scanner buffer across calls while still allowing // growth up to 1 MB for large frontmatter values or long base64-encoded lines. - scannerBufferPtr := workflowTitleScannerBufferPool.Get().(*[]byte) + pooled := workflowTitleScannerBufferPool.Get() + scannerBufferPtr, ok := pooled.(*[]byte) + if !ok || scannerBufferPtr == nil { + fallback := make([]byte, workflowTitleScannerBufferSize) + scannerBufferPtr = &fallback + } scannerBuffer := *scannerBufferPtr if cap(scannerBuffer) != workflowTitleScannerBufferSize { scannerBuffer = make([]byte, workflowTitleScannerBufferSize) @@ -404,10 +409,10 @@ func extractWorkflowNameFromFile(filePath string) (string, error) { if err != nil { return "", err } + defer fd.Close() title, err := fastParseTitleFromReader(fd) if err != nil { - _ = fd.Close() return "", err } @@ -427,10 +432,6 @@ func extractWorkflowNameFromFile(filePath string) (string, error) { title = strings.Join(words, " ") } - if closeErr := fd.Close(); closeErr != nil { - return title, fmt.Errorf("failed to close workflow file %s: %w", filePath, closeErr) - } - return title, nil } diff --git a/pkg/linters/ctxbackground/ctxbackground.go b/pkg/linters/ctxbackground/ctxbackground.go index 5b59ed0c579..9fe064d5d2a 100644 --- a/pkg/linters/ctxbackground/ctxbackground.go +++ b/pkg/linters/ctxbackground/ctxbackground.go @@ -4,6 +4,7 @@ package ctxbackground import ( + "fmt" "go/ast" "go/types" "slices" @@ -25,7 +26,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), diff --git a/pkg/linters/errormessage/errormessage.go b/pkg/linters/errormessage/errormessage.go index 7f496bac704..bd282ae8892 100644 --- a/pkg/linters/errormessage/errormessage.go +++ b/pkg/linters/errormessage/errormessage.go @@ -3,6 +3,7 @@ package errormessage import ( + "fmt" "go/ast" "go/token" "path/filepath" @@ -42,7 +43,10 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } noLintLinesByFile := nolint.BuildLineIndex(pass, "errormessage") nodeFilter := []ast.Node{(*ast.CallExpr)(nil)} diff --git a/pkg/linters/errstringmatch/errstringmatch.go b/pkg/linters/errstringmatch/errstringmatch.go index c8256f0049d..a83ccb72beb 100644 --- a/pkg/linters/errstringmatch/errstringmatch.go +++ b/pkg/linters/errstringmatch/errstringmatch.go @@ -4,6 +4,7 @@ package errstringmatch import ( + "fmt" "go/ast" "go/token" "go/types" @@ -26,7 +27,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } noLintLinesByFile := nolint.BuildLineIndex(pass, "errstringmatch") nodeFilter := []ast.Node{ diff --git a/pkg/linters/excessivefuncparams/excessivefuncparams.go b/pkg/linters/excessivefuncparams/excessivefuncparams.go index a3259c78d27..eb241a37dfd 100644 --- a/pkg/linters/excessivefuncparams/excessivefuncparams.go +++ b/pkg/linters/excessivefuncparams/excessivefuncparams.go @@ -3,6 +3,7 @@ package excessivefuncparams import ( + "fmt" "go/ast" "golang.org/x/tools/go/analysis" @@ -31,14 +32,20 @@ func init() { } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), } insp.Preorder(nodeFilter, func(n ast.Node) { - fn := n.(*ast.FuncDecl) + fn, ok := n.(*ast.FuncDecl) + if !ok { + return + } if fn.Type == nil || fn.Type.Params == nil { return } diff --git a/pkg/linters/fileclosenotdeferred/fileclosenotdeferred.go b/pkg/linters/fileclosenotdeferred/fileclosenotdeferred.go index acf2c62c838..b609ad4ec71 100644 --- a/pkg/linters/fileclosenotdeferred/fileclosenotdeferred.go +++ b/pkg/linters/fileclosenotdeferred/fileclosenotdeferred.go @@ -3,6 +3,7 @@ package fileclosenotdeferred import ( + "fmt" "go/ast" "go/token" "go/types" @@ -24,7 +25,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), diff --git a/pkg/linters/fprintlnsprintf/fprintlnsprintf.go b/pkg/linters/fprintlnsprintf/fprintlnsprintf.go index 52f7253ece4..264b99f175a 100644 --- a/pkg/linters/fprintlnsprintf/fprintlnsprintf.go +++ b/pkg/linters/fprintlnsprintf/fprintlnsprintf.go @@ -3,6 +3,7 @@ package fprintlnsprintf import ( + "fmt" "go/ast" "golang.org/x/tools/go/analysis" @@ -22,7 +23,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), diff --git a/pkg/linters/largefunc/largefunc.go b/pkg/linters/largefunc/largefunc.go index 0ea54c8e22d..7dfc2afc63c 100644 --- a/pkg/linters/largefunc/largefunc.go +++ b/pkg/linters/largefunc/largefunc.go @@ -3,6 +3,7 @@ package largefunc import ( + "fmt" "go/ast" "golang.org/x/tools/go/analysis" @@ -31,7 +32,10 @@ func init() { } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), diff --git a/pkg/linters/manualmutexunlock/manualmutexunlock.go b/pkg/linters/manualmutexunlock/manualmutexunlock.go index a49f47c0469..b58bbce5333 100644 --- a/pkg/linters/manualmutexunlock/manualmutexunlock.go +++ b/pkg/linters/manualmutexunlock/manualmutexunlock.go @@ -4,6 +4,7 @@ package manualmutexunlock import ( + "fmt" "go/ast" "go/token" "go/types" @@ -25,7 +26,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), diff --git a/pkg/linters/osexitinlibrary/osexitinlibrary.go b/pkg/linters/osexitinlibrary/osexitinlibrary.go index 4d5a2b39e25..1c8511d210b 100644 --- a/pkg/linters/osexitinlibrary/osexitinlibrary.go +++ b/pkg/linters/osexitinlibrary/osexitinlibrary.go @@ -3,6 +3,7 @@ package osexitinlibrary import ( + "fmt" "go/ast" "strings" @@ -29,14 +30,20 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } insp.Preorder(nodeFilter, func(n ast.Node) { - call := n.(*ast.CallExpr) + call, ok := n.(*ast.CallExpr) + if !ok { + return + } if strings.HasSuffix(pkgPath, ".test") || filecheck.IsTestFile(pass.Fset.Position(call.Pos()).Filename) { return } diff --git a/pkg/linters/ossetenvlibrary/ossetenvlibrary.go b/pkg/linters/ossetenvlibrary/ossetenvlibrary.go index e328cda917f..dffa4293103 100644 --- a/pkg/linters/ossetenvlibrary/ossetenvlibrary.go +++ b/pkg/linters/ossetenvlibrary/ossetenvlibrary.go @@ -3,6 +3,7 @@ package ossetenvlibrary import ( + "fmt" "go/ast" "go/types" "strings" @@ -29,14 +30,20 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } insp.Preorder(nodeFilter, func(n ast.Node) { - call := n.(*ast.CallExpr) + call, ok := n.(*ast.CallExpr) + if !ok { + return + } if strings.HasSuffix(pkgPath, ".test") || filecheck.IsTestFile(pass.Fset.PositionFor(call.Pos(), false).Filename) { return diff --git a/pkg/linters/panic-in-library-code/panic-in-library-code.go b/pkg/linters/panic-in-library-code/panic-in-library-code.go index 97139680d71..5a9d80ecc93 100644 --- a/pkg/linters/panic-in-library-code/panic-in-library-code.go +++ b/pkg/linters/panic-in-library-code/panic-in-library-code.go @@ -3,6 +3,7 @@ package panicinlibrarycode import ( + "fmt" "go/ast" "go/constant" "go/token" @@ -33,7 +34,10 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), @@ -44,7 +48,10 @@ func run(pass *analysis.Pass) (any, error) { return true } - call := n.(*ast.CallExpr) + call, ok := n.(*ast.CallExpr) + if !ok { + return true + } // Skip test files if strings.HasSuffix(pkgPath, ".test") || filecheck.IsTestFile(pass.Fset.Position(call.Pos()).Filename) { return true diff --git a/pkg/linters/rawloginlib/rawloginlib.go b/pkg/linters/rawloginlib/rawloginlib.go index 1e4d702f424..7f44aaa8b27 100644 --- a/pkg/linters/rawloginlib/rawloginlib.go +++ b/pkg/linters/rawloginlib/rawloginlib.go @@ -3,6 +3,7 @@ package rawloginlib import ( + "fmt" "go/ast" "strings" @@ -34,12 +35,18 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{(*ast.CallExpr)(nil)} insp.Preorder(nodeFilter, func(n ast.Node) { - call := n.(*ast.CallExpr) + call, ok := n.(*ast.CallExpr) + if !ok { + return + } if filecheck.IsTestFile(pass.Fset.Position(call.Pos()).Filename) { return } diff --git a/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go b/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go index 454910a19e5..93d0e7270c8 100644 --- a/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go +++ b/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go @@ -4,6 +4,7 @@ package regexpcompileinfunction import ( + "fmt" "go/ast" "go/token" "go/types" @@ -26,7 +27,10 @@ var Analyzer = &analysis.Analyzer{ } func run(pass *analysis.Pass) (any, error) { - insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + insp, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + if !ok { + return nil, fmt.Errorf("inspect analyzer result has unexpected type %T", pass.ResultOf[inspect.Analyzer]) + } nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), diff --git a/pkg/parser/virtual_fs.go b/pkg/parser/virtual_fs.go index 6dee4d72ca1..5b9472ce67e 100644 --- a/pkg/parser/virtual_fs.go +++ b/pkg/parser/virtual_fs.go @@ -72,8 +72,14 @@ func GetBuiltinFrontmatterCache(path string) (*FrontmatterResult, bool) { virtualFsLog.Printf("Frontmatter cache miss: path=%s", path) return nil, false } + result, ok := v.(*FrontmatterResult) + if !ok { + virtualFsLog.Printf("Frontmatter cache type mismatch for %s: got %T", path, v) + builtinFrontmatterCache.Delete(path) + return nil, false + } virtualFsLog.Printf("Frontmatter cache hit: path=%s", path) - return v.(*FrontmatterResult), true + return result, true } // SetBuiltinFrontmatterCache stores a FrontmatterResult for a builtin virtual file. @@ -87,7 +93,13 @@ func SetBuiltinFrontmatterCache(path string, result *FrontmatterResult) *Frontma } else { virtualFsLog.Printf("Frontmatter cache stored: path=%s", path) } - return actual.(*FrontmatterResult) + cached, ok := actual.(*FrontmatterResult) + if !ok { + virtualFsLog.Printf("Frontmatter cache type mismatch for %s: got %T", path, actual) + builtinFrontmatterCache.Store(path, result) + return result + } + return cached } // BuiltinPathPrefix is the path prefix used for embedded builtin files. diff --git a/pkg/workflow/dispatch_repository.go b/pkg/workflow/dispatch_repository.go index 648e48bf2f8..5e29748aec8 100644 --- a/pkg/workflow/dispatch_repository.go +++ b/pkg/workflow/dispatch_repository.go @@ -161,7 +161,9 @@ func generateDispatchRepositoryTool(toolKey string, toolConfig *DispatchReposito } if len(required) > 0 { - tool["inputSchema"].(map[string]any)["required"] = required + if inputSchema, ok := tool["inputSchema"].(map[string]any); ok { + inputSchema["required"] = required + } } dispatchRepositoryLog.Printf("Generated dispatch_repository tool: name=%s, properties=%d", toolName, len(properties)) diff --git a/pkg/workflow/mcp_scripts_generator.go b/pkg/workflow/mcp_scripts_generator.go index dfe124044fe..791172e7595 100644 --- a/pkg/workflow/mcp_scripts_generator.go +++ b/pkg/workflow/mcp_scripts_generator.go @@ -58,7 +58,11 @@ func GenerateMCPScriptsToolsConfig(mcpScripts *MCPScriptsConfig) string { "properties": make(map[string]any), } - props := inputSchema["properties"].(map[string]any) + props, ok := inputSchema["properties"].(map[string]any) + if !ok { + props = make(map[string]any) + inputSchema["properties"] = props + } var required []string // Sort input names for stable output diff --git a/pkg/workflow/repository_features_validation.go b/pkg/workflow/repository_features_validation.go index 3d0002ec3cc..22563826a94 100644 --- a/pkg/workflow/repository_features_validation.go +++ b/pkg/workflow/repository_features_validation.go @@ -182,7 +182,11 @@ func getCurrentRepositoryUncached() (string, error) { func getRepositoryFeatures(repo string, verbose bool) (*RepositoryFeatures, error) { // Check cache first using sync.Map if cached, exists := repositoryFeaturesCache.Load(repo); exists { - features := cached.(*RepositoryFeatures) + features, ok := cached.(*RepositoryFeatures) + if !ok { + repositoryFeaturesCache.Delete(repo) + return nil, fmt.Errorf("invalid repository feature cache entry for %s: got %T", repo, cached) + } repositoryFeaturesLog.Printf("Using cached repository features for: %s", repo) return features, nil } @@ -209,7 +213,11 @@ func getRepositoryFeatures(repo string, verbose bool) (*RepositoryFeatures, erro // Cache the result using sync.Map's LoadOrStore for atomic caching // This handles the race condition where multiple goroutines might fetch the same repo actual, loaded := repositoryFeaturesCache.LoadOrStore(repo, features) - actualFeatures := actual.(*RepositoryFeatures) + actualFeatures, ok := actual.(*RepositoryFeatures) + if !ok { + repositoryFeaturesCache.Delete(repo) + return nil, fmt.Errorf("invalid repository feature cache entry for %s: got %T", repo, actual) + } repositoryFeaturesLog.Printf("Cached repository features for: %s (discussions: %v, issues: %v)", repo, actualFeatures.HasDiscussions, actualFeatures.HasIssues) diff --git a/pkg/workflow/safe_outputs_actions.go b/pkg/workflow/safe_outputs_actions.go index 7741557f36e..23bbe6c423b 100644 --- a/pkg/workflow/safe_outputs_actions.go +++ b/pkg/workflow/safe_outputs_actions.go @@ -408,7 +408,11 @@ func generateActionToolDefinition(actionName string, config *SafeOutputActionCon } var requiredFields []string - properties := inputSchema["properties"].(map[string]any) + properties, ok := inputSchema["properties"].(map[string]any) + if !ok { + properties = make(map[string]any) + inputSchema["properties"] = properties + } // Sort for deterministic output inputNames := make([]string, 0, len(config.Inputs)) diff --git a/pkg/workflow/safe_outputs_config_generation.go b/pkg/workflow/safe_outputs_config_generation.go index aed710553ac..dcb3e27b154 100644 --- a/pkg/workflow/safe_outputs_config_generation.go +++ b/pkg/workflow/safe_outputs_config_generation.go @@ -240,7 +240,11 @@ func generateCustomJobToolDefinition(jobName string, jobConfig *SafeJobConfig) m } var requiredFields []string - properties := inputSchema["properties"].(map[string]any) + properties, ok := inputSchema["properties"].(map[string]any) + if !ok { + properties = make(map[string]any) + inputSchema["properties"] = properties + } for inputName, inputDef := range jobConfig.Inputs { property := map[string]any{} diff --git a/pkg/workflow/safe_outputs_workflow_helpers.go b/pkg/workflow/safe_outputs_workflow_helpers.go index e3ac4de9e99..fd7ed2bb87f 100644 --- a/pkg/workflow/safe_outputs_workflow_helpers.go +++ b/pkg/workflow/safe_outputs_workflow_helpers.go @@ -38,7 +38,9 @@ func generateWorkflowToolDefinition(opts workflowToolDefinitionOptions) map[stri if len(required) > 0 { sort.Strings(required) - tool["inputSchema"].(map[string]any)["required"] = required + if inputSchema, ok := tool["inputSchema"].(map[string]any); ok { + inputSchema["required"] = required + } safeOutputsWorkflowHelpersLog.Printf("Workflow tool %s has %d required inputs", toolName, len(required)) } diff --git a/pkg/workflow/yaml.go b/pkg/workflow/yaml.go index 078bd0a02de..31686234288 100644 --- a/pkg/workflow/yaml.go +++ b/pkg/workflow/yaml.go @@ -170,7 +170,13 @@ func UnquoteYAMLKey(yamlStr string, key string) string { // Use cached compiled regex to avoid recompiling on every call var re *regexp.Regexp if cached, ok := unquoteYAMLKeyCache.Load(key); ok { - re = cached.(*regexp.Regexp) + var typeOK bool + re, typeOK = cached.(*regexp.Regexp) + if !typeOK { + unquoteYAMLKeyCache.Delete(key) + re = regexp.MustCompile(pattern) + unquoteYAMLKeyCache.Store(key, re) + } } else { re = regexp.MustCompile(pattern) unquoteYAMLKeyCache.Store(key, re) From fe750de93c160a6255ebf3c78d85ae109a739db3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 04:13:52 +0000 Subject: [PATCH 3/6] Address review feedback for close and cache errors Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/cli/workflows.go | 10 +++++++--- pkg/workflow/repository_features_validation.go | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pkg/cli/workflows.go b/pkg/cli/workflows.go index 74808f6677b..ac9357aa8e8 100644 --- a/pkg/cli/workflows.go +++ b/pkg/cli/workflows.go @@ -404,14 +404,18 @@ func fastParseTitleFromReader(r io.Reader) (string, error) { } // extractWorkflowNameFromFile extracts the workflow name from a file's H1 header -func extractWorkflowNameFromFile(filePath string) (string, error) { +func extractWorkflowNameFromFile(filePath string) (title string, err error) { fd, err := os.Open(filePath) if err != nil { return "", err } - defer fd.Close() + defer func() { + if closeErr := fd.Close(); closeErr != nil && err == nil { + err = fmt.Errorf("failed to close workflow file %s: %w", filePath, closeErr) + } + }() - title, err := fastParseTitleFromReader(fd) + title, err = fastParseTitleFromReader(fd) if err != nil { return "", err } diff --git a/pkg/workflow/repository_features_validation.go b/pkg/workflow/repository_features_validation.go index 22563826a94..e8b2f4e6c15 100644 --- a/pkg/workflow/repository_features_validation.go +++ b/pkg/workflow/repository_features_validation.go @@ -185,7 +185,7 @@ func getRepositoryFeatures(repo string, verbose bool) (*RepositoryFeatures, erro features, ok := cached.(*RepositoryFeatures) if !ok { repositoryFeaturesCache.Delete(repo) - return nil, fmt.Errorf("invalid repository feature cache entry for %s: got %T", repo, cached) + return nil, fmt.Errorf("invalid repository feature cache entry for %s: expected *RepositoryFeatures, got %T", repo, cached) } repositoryFeaturesLog.Printf("Using cached repository features for: %s", repo) return features, nil @@ -216,7 +216,7 @@ func getRepositoryFeatures(repo string, verbose bool) (*RepositoryFeatures, erro actualFeatures, ok := actual.(*RepositoryFeatures) if !ok { repositoryFeaturesCache.Delete(repo) - return nil, fmt.Errorf("invalid repository feature cache entry for %s: got %T", repo, actual) + return nil, fmt.Errorf("invalid repository feature cache entry for %s: expected *RepositoryFeatures, got %T", repo, actual) } repositoryFeaturesLog.Printf("Cached repository features for: %s (discussions: %v, issues: %v)", repo, actualFeatures.HasDiscussions, actualFeatures.HasIssues) From c69ae3916dd3d5c041cf140191a8380302cf1de8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 04:58:38 +0000 Subject: [PATCH 4/6] docs(adr): add draft ADR-35111 for runtime type assertion hardening Auto-generated by the Design Decision Gate. The PR author should review, complete, and finalize this document before merging. Co-Authored-By: Claude Opus 4.7 (1M context) --- ...-type-assertions-with-per-site-recovery.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 docs/adr/35111-harden-runtime-type-assertions-with-per-site-recovery.md diff --git a/docs/adr/35111-harden-runtime-type-assertions-with-per-site-recovery.md b/docs/adr/35111-harden-runtime-type-assertions-with-per-site-recovery.md new file mode 100644 index 00000000000..505345b5927 --- /dev/null +++ b/docs/adr/35111-harden-runtime-type-assertions-with-per-site-recovery.md @@ -0,0 +1,91 @@ +# ADR-35111: Harden Runtime Type Assertions With Per-Site Recovery + +**Date**: 2026-05-27 +**Status**: Draft +**Deciders**: Unknown — generated by the Design Decision Gate from PR #35111 evidence + +--- + +## Part 1 — Narrative (Human-Friendly) + +### Context + +The repository has accumulated a number of single-value Go type assertions of the form `v := x.(T)` against values pulled from `sync.Map` caches, `map[string]any` schema fragments, and `analysis.Pass.ResultOf`. Each of these panics on a type mismatch, which would take down a long-lived linter pass, compile, or workflow update operation rather than surface a recoverable error. The repo's custom `uncheckedtypeassertion` linter (see [ADR-34738](34738-add-uncheckedtypeassertion-linter.md)) flagged 31 such sites across `pkg/parser`, `pkg/workflow`, `pkg/cli`, and `pkg/linters/*`. At the same time the `excessivefuncparams` and `fileclosenotdeferred` linters flagged one site each in `pkg/cli/update_manifest.go` and `pkg/cli/workflows.go`. We needed a consistent remediation strategy that converts panics into appropriate, context-specific behavior without losing diagnostic signal. + +### Decision + +We will **replace every flagged unchecked single-value type assertion with a two-value assertion plus per-call-site recovery**, choosing one of four recovery modes based on what the call site can do safely: + +1. **Return an error** when the call site already has an `error` return and a typed cache invariant has been violated (e.g. `repositoryFeaturesCache`). +2. **Drop the bad cache entry and reconstruct** when the cache is local, recomputable, and self-healing is safe (e.g. `unquoteYAMLKeyCache`, `builtinFrontmatterCache`). +3. **Append a structured warning and skip the affected output** when the function aggregates warnings and partial output is acceptable (e.g. `convertTriggersToOn`). +4. **Initialize a fresh map / slice** when a schema-shaped `map[string]any` is malformed but the function's job is to construct it (e.g. `generateActionToolDefinition`, `generateCustomJobToolDefinition`, `generateMCPScriptsToolsConfig`). + +In the same change set we also adopt two narrower decisions for the other two lint findings: `updateManifestManagedWorkflow` is refactored to take a single `manifestManagedWorkflowUpdate` struct instead of nine positional parameters, and `extractWorkflowNameFromFile` is switched to named return values + a single deferred `Close` so the close error is propagated through the named `err` rather than racing the success path. + +### Alternatives Considered + +#### Alternative 1: Centralized helper (`mustAssert[T any](v any) (T, error)`) + +A single generic helper would have offered one canonical code shape at every site. We rejected it because the *recovery* behavior is what actually varies — half the sites need to fall back to a fresh value, a quarter need to delete a cache entry, and the rest need to either return or warn. A helper that only produced `(T, error)` would have forced every caller to reinvent its recovery logic anyway, and a helper rich enough to encode all four modes would be more complex than the inline two-value pattern. + +#### Alternative 2: Silence the linter with `//nolint:uncheckedtypeassertion` + +For internally-controlled caches we technically know the stored type. Suppressing the linter would have been the smallest diff. We rejected this because (a) the panic-on-mismatch failure mode is unacceptable for long-running processes — a compiler-pipeline panic blows up an entire workflow run — and (b) suppression hides the same class of bug from future contributors who modify cache-population sites without realizing a reader will panic on the new type. + +#### Alternative 3: Recover panics at the boundary + +We could have left the assertions unchecked and added `defer recover()` at job/analyzer boundaries. We rejected this because `recover` loses the static type information at the site, makes test-time failures harder to localize, and conflates type-invariant bugs with all other runtime panics. + +### Consequences + +#### Positive +- Runtime type-invariant violations now surface as targeted errors or warnings instead of process-killing panics. +- Self-healing caches (`unquoteYAMLKeyCache`, `builtinFrontmatterCache`) survive corruption without restart. +- Linter remains green, making it a reliable gate for new code. +- The struct-parameter refactor of `updateManifestManagedWorkflow` makes the call site at `pkg/cli/update_manifest.go` materially easier to read and extend. +- File-close errors from `extractWorkflowNameFromFile` are now propagated even on the success path. + +#### Negative +- Four recovery modes instead of one increases reviewer cognitive load — readers must check that the *right* mode was chosen for each site. +- A few sites now silently degrade (e.g. dropping a schedule trigger with only a warning); a missed warning channel could mask real bugs. +- Cache "re-store on type mismatch" introduces a small extra write on the corruption path; benign but not free. +- The `manifestManagedWorkflowUpdate` struct adds a small layer of indirection at every field read inside `updateManifestManagedWorkflow`. + +#### Neutral +- No public API change — all modifications are internal to `pkg/`. +- The struct refactor is mechanically pure: parameter values are identical, only their packaging changes. +- The defer-based close in `extractWorkflowNameFromFile` requires named return values, which is a style choice the rest of the package already uses. + +--- + +## Part 2 — Normative Specification (RFC 2119) + +> The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**, **SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **MAY**, and **OPTIONAL** in this section are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +### Type Assertions in Library and Compiler Code + +1. New code in `pkg/` **MUST NOT** use the single-value form `v := x.(T)` for values read from `any`-typed containers (including `sync.Map`, `map[string]any`, and `analysis.Pass.ResultOf`). +2. New code in `pkg/` **MUST** use the two-value form `v, ok := x.(T)` and explicitly handle the `!ok` branch when reading from any such container. +3. The `!ok` branch **MUST** choose exactly one of: returning an error, deleting and reconstructing the cache entry, appending a structured warning and continuing with degraded output, or initializing a fresh container. +4. Implementations **MUST NOT** suppress the `uncheckedtypeassertion` linter via `//nolint` comments to avoid handling the `!ok` case. +5. Implementations **MAY** keep the single-value form for assertions against typed values obtained from `reflect` or where the static type is provably non-interface (e.g. concrete struct field access via type switch arms). + +### Function Parameter Lists + +1. Functions in `pkg/cli/` **SHOULD NOT** take more than the `excessivefuncparams` linter's configured limit of positional parameters. +2. When the limit would be exceeded, implementations **SHOULD** group cohesive parameters into a single options or update struct, as done with `manifestManagedWorkflowUpdate`. + +### File Handles and Deferred Close + +1. Functions that `os.Open` a file **MUST** call `Close` via `defer` immediately after a successful open. +2. Functions that need to propagate a close error on the success path **SHOULD** use named return values and assign the close error to the named `err` from within the deferred function. +3. Implementations **MUST NOT** rely on a trailing non-deferred `Close` at the end of a function as the sole close path. + +### Conformance + +An implementation is considered conformant with this ADR if it satisfies all **MUST** and **MUST NOT** requirements above. The `uncheckedtypeassertion`, `excessivefuncparams`, and `fileclosenotdeferred` linters in `pkg/linters/` are the executable conformance check for the corresponding sections. + +--- + +*This is a DRAFT ADR generated by the [Design Decision Gate](https://github.com/github/gh-aw/actions/runs/26491524257) workflow. The PR author must review, complete, and finalize this document before the PR can merge.* From 9ef54076fe7c35049f0b19ec85ee0e056e47dc6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 05:10:19 +0000 Subject: [PATCH 5/6] Remove redundant type assertions by binding inputSchema to local variable Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/workflow/dispatch_repository.go | 22 ++++++++--------- pkg/workflow/safe_outputs_workflow_helpers.go | 24 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/pkg/workflow/dispatch_repository.go b/pkg/workflow/dispatch_repository.go index 5e29748aec8..05bcdd558e2 100644 --- a/pkg/workflow/dispatch_repository.go +++ b/pkg/workflow/dispatch_repository.go @@ -149,21 +149,21 @@ func generateDispatchRepositoryTool(toolKey string, toolConfig *DispatchReposito return "Input parameter '" + inputName + "'" }) + inputSchema := map[string]any{ + "type": "object", + "properties": properties, + "additionalProperties": false, + } + + if len(required) > 0 { + inputSchema["required"] = required + } + tool := map[string]any{ "name": toolName, "description": description, "_dispatch_repository_tool": toolKey, // Internal metadata for handler routing - "inputSchema": map[string]any{ - "type": "object", - "properties": properties, - "additionalProperties": false, - }, - } - - if len(required) > 0 { - if inputSchema, ok := tool["inputSchema"].(map[string]any); ok { - inputSchema["required"] = required - } + "inputSchema": inputSchema, } dispatchRepositoryLog.Printf("Generated dispatch_repository tool: name=%s, properties=%d", toolName, len(properties)) diff --git a/pkg/workflow/safe_outputs_workflow_helpers.go b/pkg/workflow/safe_outputs_workflow_helpers.go index fd7ed2bb87f..a287cb3752a 100644 --- a/pkg/workflow/safe_outputs_workflow_helpers.go +++ b/pkg/workflow/safe_outputs_workflow_helpers.go @@ -25,25 +25,25 @@ func generateWorkflowToolDefinition(opts workflowToolDefinitionOptions) map[stri return fmt.Sprintf("Input parameter '%s' for workflow %s", inputName, opts.workflowName) }) - tool := map[string]any{ - "name": toolName, - "description": description, - opts.metadataKey: opts.workflowName, - "inputSchema": map[string]any{ - "type": "object", - "properties": properties, - "additionalProperties": false, - }, + inputSchema := map[string]any{ + "type": "object", + "properties": properties, + "additionalProperties": false, } if len(required) > 0 { sort.Strings(required) - if inputSchema, ok := tool["inputSchema"].(map[string]any); ok { - inputSchema["required"] = required - } + inputSchema["required"] = required safeOutputsWorkflowHelpersLog.Printf("Workflow tool %s has %d required inputs", toolName, len(required)) } + tool := map[string]any{ + "name": toolName, + "description": description, + opts.metadataKey: opts.workflowName, + "inputSchema": inputSchema, + } + return tool } From 048af60311c3d4dc9ab85e160e54da29ac405031 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 27 May 2026 10:20:23 +0000 Subject: [PATCH 6/6] Merge origin/main and recompile daily-compiler-threat-spec-optimizer to fix hash mismatch Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- ...ly-compiler-threat-spec-optimizer.lock.yml | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml index dc7ee0fa391..91a33f21d9d 100644 --- a/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml +++ b/.github/workflows/daily-compiler-threat-spec-optimizer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"92d4db5190674780e53e1c9dc02d7ec3b44e9b37467aa8c5d7599fb7ccfe033e","body_hash":"8761535078c69ec3df664e51b40b25d49c9704cc69a8a6bfe003ffe4409bd20c","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"3318b7415c6a6e13a76c9ab75cf996e2c7ef63ebbec2e216cf57489a448081b1","body_hash":"8761535078c69ec3df664e51b40b25d49c9704cc69a8a6bfe003ffe4409bd20c","strict":true,"agent_id":"copilot"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.25.55"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -62,8 +62,8 @@ name: "Daily Compiler Threat Spec Optimizer" on: schedule: - - cron: "19 2 * * *" - # Friendly format: daily (scattered) + - cron: "6 3 * * 1" + # Friendly format: weekly on monday around 03:00 (scattered) workflow_dispatch: inputs: aw_context: @@ -208,23 +208,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_6801afa46256074c_EOF' + cat << 'GH_AW_PROMPT_6bd877931be036c6_EOF' - GH_AW_PROMPT_6801afa46256074c_EOF + GH_AW_PROMPT_6bd877931be036c6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_6801afa46256074c_EOF' + cat << 'GH_AW_PROMPT_6bd877931be036c6_EOF' Tools: create_discussion, create_pull_request, missing_tool, missing_data, noop - GH_AW_PROMPT_6801afa46256074c_EOF + GH_AW_PROMPT_6bd877931be036c6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" - cat << 'GH_AW_PROMPT_6801afa46256074c_EOF' + cat << 'GH_AW_PROMPT_6bd877931be036c6_EOF' - GH_AW_PROMPT_6801afa46256074c_EOF + GH_AW_PROMPT_6bd877931be036c6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_6801afa46256074c_EOF' + cat << 'GH_AW_PROMPT_6bd877931be036c6_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -253,15 +253,15 @@ jobs: {{/if}} - GH_AW_PROMPT_6801afa46256074c_EOF + GH_AW_PROMPT_6bd877931be036c6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_6801afa46256074c_EOF' + cat << 'GH_AW_PROMPT_6bd877931be036c6_EOF' {{#runtime-import .github/workflows/shared/otlp.md}} {{#runtime-import .github/workflows/shared/reporting.md}} {{#runtime-import .github/workflows/shared/noop-reminder.md}} {{#runtime-import .github/workflows/daily-compiler-threat-spec-optimizer.md}} - GH_AW_PROMPT_6801afa46256074c_EOF + GH_AW_PROMPT_6bd877931be036c6_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -481,9 +481,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_9d020f430f3c4f8f_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_fd3adc6fcc4e382b_EOF' {"create_discussion":{"category":"audits","close_older_discussions":true,"expires":72,"fallback_to_issue":true,"max":1,"title_prefix":"[compiler-threat-spec] "},"create_pull_request":{"draft":false,"expires":168,"labels":["security","compiler","specification","automation"],"max":1,"max_patch_files":100,"max_patch_size":1024,"protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_files_policy":"request_review","title_prefix":"[compiler-threat-spec] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_9d020f430f3c4f8f_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_fd3adc6fcc4e382b_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -723,7 +723,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_e6c1f019753cc2b0_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_db88cf8bac8590ee_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "safeoutputs": { @@ -753,7 +753,7 @@ jobs: } } } - GH_AW_MCP_CONFIG_e6c1f019753cc2b0_EOF + GH_AW_MCP_CONFIG_db88cf8bac8590ee_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1346,7 +1346,7 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","registry.npmjs.org","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then