From 7b0f398f110569182dad7a8977568ba4163e3f22 Mon Sep 17 00:00:00 2001 From: beads/crew/fang Date: Mon, 5 Jan 2026 22:06:52 -0800 Subject: [PATCH] fix(lint): address gosec, misspell, and unparam warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - gate.go: fix "cancelled" → "canceled" misspelling, add #nosec for validated GitHub IDs in exec.Command, mark checkTimer escalated as intentionally false, rename unused ctx param - sync_divergence.go: add #nosec for git commands with validated paths, mark unused path param - sync_branch.go: add #nosec for .git/info/exclude permissions - setup.go: add #nosec for config file permissions - recipes.go: add #nosec for validated config file paths - external_deps.go: add #nosec for SQL with generated placeholders 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/doctor/fix/sync_branch.go | 2 +- cmd/bd/doctor/sync_divergence.go | 8 ++++---- cmd/bd/gate.go | 15 ++++++++------- cmd/bd/setup.go | 4 ++-- internal/recipes/recipes.go | 6 +++--- internal/storage/sqlite/external_deps.go | 1 + 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/cmd/bd/doctor/fix/sync_branch.go b/cmd/bd/doctor/fix/sync_branch.go index 56180dc8..ec9269bd 100644 --- a/cmd/bd/doctor/fix/sync_branch.go +++ b/cmd/bd/doctor/fix/sync_branch.go @@ -339,7 +339,7 @@ func addToGitExclude(path, pattern string) error { } // Append pattern - f, err := os.OpenFile(excludePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + f, err := os.OpenFile(excludePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) // #nosec G302 -- .git/info/exclude needs to be readable if err != nil { return fmt.Errorf("failed to open exclude file: %w", err) } diff --git a/cmd/bd/doctor/sync_divergence.go b/cmd/bd/doctor/sync_divergence.go index 4c76a9f2..4627fc6c 100644 --- a/cmd/bd/doctor/sync_divergence.go +++ b/cmd/bd/doctor/sync_divergence.go @@ -124,7 +124,7 @@ func checkJSONLGitDivergence(path, beadsDir string) *SyncDivergenceIssue { } // Check if file is tracked by git - cmd := exec.Command("git", "ls-files", "--error-unmatch", relPath) + cmd := exec.Command("git", "ls-files", "--error-unmatch", relPath) // #nosec G204 -- relPath is derived from validated file path cmd.Dir = path if err := cmd.Run(); err != nil { // File not tracked by git @@ -132,7 +132,7 @@ func checkJSONLGitDivergence(path, beadsDir string) *SyncDivergenceIssue { } // Compare current file with HEAD - cmd = exec.Command("git", "diff", "--quiet", "HEAD", "--", relPath) + cmd = exec.Command("git", "diff", "--quiet", "HEAD", "--", relPath) // #nosec G204 -- relPath is derived from validated file path cmd.Dir = path if err := cmd.Run(); err != nil { // Exit code non-zero means there are differences @@ -147,7 +147,7 @@ func checkJSONLGitDivergence(path, beadsDir string) *SyncDivergenceIssue { } // checkSQLiteMtimeDivergence checks if SQLite last_import_time matches JSONL mtime. -func checkSQLiteMtimeDivergence(path, beadsDir string) *SyncDivergenceIssue { +func checkSQLiteMtimeDivergence(_, beadsDir string) *SyncDivergenceIssue { //nolint:unparam // path reserved for future use // Get database path dbPath := filepath.Join(beadsDir, beads.CanonicalDatabaseName) if cfg, err := configfile.Load(beadsDir); err == nil && cfg != nil && cfg.Database != "" { @@ -235,7 +235,7 @@ func checkUncommittedBeadsChanges(path, beadsDir string) *SyncDivergenceIssue { } // Check for uncommitted changes in .beads/ - cmd := exec.Command("git", "status", "--porcelain", "--", relBeadsDir) + cmd := exec.Command("git", "status", "--porcelain", "--", relBeadsDir) // #nosec G204 -- relBeadsDir is derived from validated path cmd.Dir = path out, err := cmd.Output() if err != nil { diff --git a/cmd/bd/gate.go b/cmd/bd/gate.go index 61255643..8e3f0914 100644 --- a/cmd/bd/gate.go +++ b/cmd/bd/gate.go @@ -442,7 +442,7 @@ A gate is resolved when: - bead: target bead status=closed A gate is escalated when: - - gh:run: status=completed AND conclusion in (failure, cancelled) + - gh:run: status=completed AND conclusion in (failure, canceled) - gh:pr: state=CLOSED AND merged=false Examples: @@ -645,7 +645,7 @@ func checkGHRun(gate *types.Issue) (resolved, escalated bool, reason string, err } // Run: gh run view --json status,conclusion,name - cmd := exec.Command("gh", "run", "view", gate.AwaitID, "--json", "status,conclusion,name") + cmd := exec.Command("gh", "run", "view", gate.AwaitID, "--json", "status,conclusion,name") // #nosec G204 -- gate.AwaitID is a validated GitHub run ID var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -676,8 +676,8 @@ func checkGHRun(gate *types.Issue) (resolved, escalated bool, reason string, err return true, false, fmt.Sprintf("workflow '%s' succeeded", status.Name), nil case "failure": return false, true, fmt.Sprintf("workflow '%s' failed", status.Name), nil - case "cancelled": - return false, true, fmt.Sprintf("workflow '%s' was cancelled", status.Name), nil + case "cancelled", "canceled": + return false, true, fmt.Sprintf("workflow '%s' was canceled", status.Name), nil case "skipped": return true, false, fmt.Sprintf("workflow '%s' was skipped", status.Name), nil default: @@ -697,7 +697,7 @@ func checkGHPR(gate *types.Issue) (resolved, escalated bool, reason string, err } // Run: gh pr view --json state,merged,title - cmd := exec.Command("gh", "pr", "view", gate.AwaitID, "--json", "state,merged,title") + cmd := exec.Command("gh", "pr", "view", gate.AwaitID, "--json", "state,merged,title") // #nosec G204 -- gate.AwaitID is a validated GitHub PR number var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr @@ -737,7 +737,8 @@ func checkGHPR(gate *types.Issue) (resolved, escalated bool, reason string, err } // checkTimer checks a timer gate for expiration -func checkTimer(gate *types.Issue, now time.Time) (resolved, escalated bool, reason string, err error) { +// Note: timers resolve but never escalate (escalated is always false by design) +func checkTimer(gate *types.Issue, now time.Time) (resolved, escalated bool, reason string, err error) { //nolint:unparam // escalated intentionally always false if gate.Timeout == 0 { return false, false, "timer gate without timeout configured", fmt.Errorf("no timeout set") } @@ -815,7 +816,7 @@ func checkBeadGate(ctx context.Context, awaitID string) (bool, string) { } // closeGate closes a gate issue with the given reason -func closeGate(ctx interface{}, gateID, reason string) error { +func closeGate(_ interface{}, gateID, reason string) error { if daemonClient != nil { closeArgs := &rpc.CloseArgs{ ID: gateID, diff --git a/cmd/bd/setup.go b/cmd/bd/setup.go index f00f492f..74cda015 100644 --- a/cmd/bd/setup.go +++ b/cmd/bd/setup.go @@ -131,7 +131,7 @@ func writeToPath(path string) error { } } - if err := os.WriteFile(path, []byte(recipes.Template), 0o644); err != nil { + if err := os.WriteFile(path, []byte(recipes.Template), 0o644); err != nil { // #nosec G306 -- config files need to be readable return fmt.Errorf("write file: %w", err) } return nil @@ -225,7 +225,7 @@ func runRecipe(name string) { } } - if err := os.WriteFile(recipe.Path, []byte(recipes.Template), 0o644); err != nil { + if err := os.WriteFile(recipe.Path, []byte(recipes.Template), 0o644); err != nil { // #nosec G306 -- config files need to be readable fmt.Fprintf(os.Stderr, "Error: write file: %v\n", err) os.Exit(1) } diff --git a/internal/recipes/recipes.go b/internal/recipes/recipes.go index ec671bb1..4060afcf 100644 --- a/internal/recipes/recipes.go +++ b/internal/recipes/recipes.go @@ -100,7 +100,7 @@ type UserRecipes struct { // LoadUserRecipes loads recipes from .beads/recipes.toml if it exists. func LoadUserRecipes(beadsDir string) (map[string]Recipe, error) { path := filepath.Join(beadsDir, "recipes.toml") - data, err := os.ReadFile(path) + data, err := os.ReadFile(path) // #nosec G304 -- path is constructed from validated beadsDir if os.IsNotExist(err) { return nil, nil // No user recipes, that's fine } @@ -173,7 +173,7 @@ func SaveUserRecipe(beadsDir, name, path string) error { // Load existing user recipes var userRecipes UserRecipes - data, err := os.ReadFile(recipesPath) + data, err := os.ReadFile(recipesPath) // #nosec G304 -- path is constructed from validated beadsDir if err == nil { if err := toml.Unmarshal(data, &userRecipes); err != nil { return fmt.Errorf("parse recipes.toml: %w", err) @@ -199,7 +199,7 @@ func SaveUserRecipe(beadsDir, name, path string) error { } // Write back - f, err := os.Create(recipesPath) + f, err := os.Create(recipesPath) // #nosec G304 -- path is constructed from validated beadsDir if err != nil { return fmt.Errorf("create recipes.toml: %w", err) } diff --git a/internal/storage/sqlite/external_deps.go b/internal/storage/sqlite/external_deps.go index 9a1dbeeb..828c1927 100644 --- a/internal/storage/sqlite/external_deps.go +++ b/internal/storage/sqlite/external_deps.go @@ -257,6 +257,7 @@ func checkProjectCapabilities(ctx context.Context, project string, capabilities } // Query returns which provides: labels exist on closed issues + // #nosec G202 -- placeholders are generated as "?" markers, not user input query := ` SELECT DISTINCT l.label FROM labels l JOIN issues i ON l.issue_id = i.id