From c710a90bee6f8889d48bdb82cc5109bdd6eae1a8 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:11:57 +0100 Subject: [PATCH 01/15] Enable additional golangci-lint linters with zero issues Enable the following new linters: - asasalint: Check for pass []any as any in variadic func(...any) - asciicheck: Check identifiers for non-ASCII symbols - bidichk: Check for dangerous unicode character sequences - decorder: Check declaration order and count - dogsled: Check assignments with too many blank identifiers - durationcheck: Check for two durations multiplied together - exptostd: Detect functions from golang.org/x/exp/ replaceable by std - fatcontext: Detect nested contexts in loops and function literals - gocheckcompilerdirectives: Check go compiler directive comments - gochecksumtype: Run exhaustiveness checks on sum types - gomodguard: Allow/blocklist for direct Go module dependencies - goprintffuncname: Check printf-like functions are named with f - grouper: Analyze expression groups - iface: Detect incorrect use of interfaces - loggercheck: Check key-value pairs for common logger libraries - predeclared: Find code that shadows predeclared identifiers - reassign: Check that package variables are not reassigned - sloglint: Ensure consistent code style with log/slog - testableexamples: Check if examples are testable - usetesting: Report uses of functions with replacement in testing Also alphabetically sort the linter list for maintainability. Assisted-By: cagent --- .golangci.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f7e02cba7..8f4656948 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,30 +3,50 @@ run: tests: true linters: enable: + - asasalint + - asciicheck + - bidichk - containedctx - copyloopvar + - decorder - depguard + - dogsled + - durationcheck - errcheck + - exptostd + - fatcontext + - forbidigo - ginkgolinter + - gocheckcompilerdirectives + - gochecknoinits + - gochecksumtype - gocritic + - gomodguard + - goprintffuncname - govet + - grouper + - iface - importas # Enforces consistent import aliases. - ineffassign - intrange + - iotamixing + - loggercheck - misspell - nakedret - nolintlint + - predeclared + - reassign - revive + - sloglint - staticcheck + - testableexamples - testifylint - thelper - unconvert - unparam - unused - usestdlibvars - - forbidigo - - iotamixing - - gochecknoinits + - usetesting settings: forbidigo: forbid: From 2395623afbad4bc2b54b9420b4d120835fad755f Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:12:15 +0100 Subject: [PATCH 02/15] Enable unqueryvet linter Detects SELECT * in SQL queries, encouraging explicit column selection. Assisted-By: cagent --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index 8f4656948..3700722de 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -46,6 +46,7 @@ linters: - unparam - unused - usestdlibvars + - unqueryvet - usetesting settings: forbidigo: From 7677df203ea151ac712e2414b21b899e58a55ab2 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:12:53 +0100 Subject: [PATCH 03/15] Enable mirror linter and fix its single issue Fix: use builder.Write(formatted) instead of builder.WriteString(string(formatted)) to avoid unnecessary allocation in transcript.go. Assisted-By: cagent --- .golangci.yml | 1 + pkg/app/transcript/transcript.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 3700722de..b4f00582a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,6 +32,7 @@ linters: - iotamixing - loggercheck - misspell + - mirror - nakedret - nolintlint - predeclared diff --git a/pkg/app/transcript/transcript.go b/pkg/app/transcript/transcript.go index c1e65713c..91a18a206 100644 --- a/pkg/app/transcript/transcript.go +++ b/pkg/app/transcript/transcript.go @@ -87,7 +87,7 @@ func toJSONString(builder *strings.Builder, in string) { if err := json.Unmarshal([]byte(in), &content); err == nil { if formatted, err := json.MarshalIndent(content, "", " "); err == nil { builder.WriteString("```json\n") - builder.WriteString(string(formatted)) + builder.Write(formatted) builder.WriteString("\n```\n") } else { builder.WriteString(in) From cfa4842de96abb1b87046af208e5b120e0cfd43f Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:14:16 +0100 Subject: [PATCH 04/15] Enable whitespace linter and fix 2 issues Fix unnecessary trailing newline in filesystem.go and unnecessary leading newline in mcp.go. Assisted-By: cagent --- .golangci.yml | 1 + pkg/tools/builtin/filesystem.go | 1 - pkg/tools/mcp/mcp.go | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index b4f00582a..b442751b6 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -49,6 +49,7 @@ linters: - usestdlibvars - unqueryvet - usetesting + - whitespace settings: forbidigo: forbid: diff --git a/pkg/tools/builtin/filesystem.go b/pkg/tools/builtin/filesystem.go index 25e2a8159..9581243ae 100644 --- a/pkg/tools/builtin/filesystem.go +++ b/pkg/tools/builtin/filesystem.go @@ -299,7 +299,6 @@ func (t *FilesystemTool) executePostEditCommands(ctx context.Context, filePath s if err := cmd.Run(); err != nil { return fmt.Errorf("post-edit command failed for %s: %w", filePath, err) } - } return nil } diff --git a/pkg/tools/mcp/mcp.go b/pkg/tools/mcp/mcp.go index 867843509..4596d9e60 100644 --- a/pkg/tools/mcp/mcp.go +++ b/pkg/tools/mcp/mcp.go @@ -124,7 +124,6 @@ func (ts *Toolset) doStart(ctx context.Context) error { // // Only retry when initialization fails due to sending the initialized notification. if !isInitNotificationSendError(err) { - // EOF means the MCP server is unavailable or closed the connection. // This is not a fatal error and should not fail the agent execution. if errors.Is(err, io.EOF) { From 768e64da40a6da11299863536972686efb0ddb06 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:14:35 +0100 Subject: [PATCH 05/15] Enable nilnesserr linter Reports constructs that check for err != nil but return a different nil value error. Assisted-By: cagent --- .golangci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.golangci.yml b/.golangci.yml index b442751b6..24ced8911 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -35,6 +35,7 @@ linters: - mirror - nakedret - nolintlint + - nilnesserr - predeclared - reassign - revive From c7f11b06eac124f6c74646b1113a92ec1ace08f7 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:15:33 +0100 Subject: [PATCH 06/15] Enable wastedassign linter and fix 3 issues Fix wasted assignments in oauth.go (use var instead of := for variables that are immediately reassigned) and fast_renderer.go (same pattern). Assisted-By: cagent --- .golangci.yml | 1 + pkg/tools/mcp/oauth.go | 4 ++-- pkg/tui/components/markdown/fast_renderer.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 24ced8911..4760a96d4 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -51,6 +51,7 @@ linters: - unqueryvet - usetesting - whitespace + - wastedassign settings: forbidigo: forbid: diff --git a/pkg/tools/mcp/oauth.go b/pkg/tools/mcp/oauth.go index 0f74e237e..5502a77ef 100644 --- a/pkg/tools/mcp/oauth.go +++ b/pkg/tools/mcp/oauth.go @@ -272,8 +272,8 @@ func (t *oauthTransport) handleManagedOAuthFlow(ctx context.Context, authServer, redirectURI := callbackServer.GetRedirectURI() slog.Debug("Using redirect URI", "uri", redirectURI) - clientID := "" - clientSecret := "" + var clientID string + var clientSecret string if authServerMetadata.RegistrationEndpoint != "" { slog.Debug("Attempting dynamic client registration") diff --git a/pkg/tui/components/markdown/fast_renderer.go b/pkg/tui/components/markdown/fast_renderer.go index be332152d..f58bc7cb7 100644 --- a/pkg/tui/components/markdown/fast_renderer.go +++ b/pkg/tui/components/markdown/fast_renderer.go @@ -2303,7 +2303,7 @@ func (p *parser) wrapText(text string, width int) string { wordWidth := ws.width // Determine if we need to wrap - only then do we need the previous styles - needsWrap := false + var needsWrap bool if wordWidth > width { needsWrap = currentLine.Len() > 0 } else { From 9eaeddeaedf338a986a73d98f2dc50cd9d1de9dd Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:16:08 +0100 Subject: [PATCH 07/15] Enable inamedparam linter and fix 1 issue Add parameter name to interface method Update(msg tea.Msg) in layout.go Model interface. Assisted-By: cagent --- .golangci.yml | 1 + pkg/tui/core/layout/layout.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.golangci.yml b/.golangci.yml index 4760a96d4..5559556ec 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -27,6 +27,7 @@ linters: - grouper - iface - importas # Enforces consistent import aliases. + - inamedparam - ineffassign - intrange - iotamixing diff --git a/pkg/tui/core/layout/layout.go b/pkg/tui/core/layout/layout.go index 54c8e9688..b00a91e6b 100644 --- a/pkg/tui/core/layout/layout.go +++ b/pkg/tui/core/layout/layout.go @@ -30,7 +30,7 @@ type Help interface { // Model is the base interface for all TUI models type Model interface { Init() tea.Cmd - Update(tea.Msg) (Model, tea.Cmd) + Update(msg tea.Msg) (Model, tea.Cmd) View() string Sizeable } From d2dd8bd6e6b9c4c6e2a7da35ae310765e3c6bfed Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:28:43 +0100 Subject: [PATCH 08/15] Enable errname linter and rename 3 error types Rename error types to follow Go convention (ErrXxx for values, XxxError for types): - ErrAutoModelFallback -> AutoModelFallbackError - ErrKeychainNotAvailable -> KeychainNotAvailableError - ErrPassNotAvailable -> PassNotAvailableError Assisted-By: cagent --- .golangci.yml | 1 + pkg/config/auto.go | 6 +++--- pkg/config/types/commands.go | 2 +- pkg/config/v0/types.go | 4 ++-- pkg/environment/keychain.go | 6 +++--- pkg/environment/pass.go | 6 +++--- pkg/teamloader/teamloader.go | 4 ++-- pkg/teamloader/teamloader_test.go | 4 ++-- 8 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 5559556ec..8add21c9f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,6 +13,7 @@ linters: - dogsled - durationcheck - errcheck + - errname - exptostd - fatcontext - forbidigo diff --git a/pkg/config/auto.go b/pkg/config/auto.go index 5b206aa95..73ff67062 100644 --- a/pkg/config/auto.go +++ b/pkg/config/auto.go @@ -32,11 +32,11 @@ var cloudProviders = []providerConfig{ }, "AWS_ACCESS_KEY_ID (or AWS_PROFILE, AWS_ROLE_ARN, AWS_BEARER_TOKEN_BEDROCK)"}, } -// ErrAutoModelFallback is returned when auto model selection fails because +// AutoModelFallbackError is returned when auto model selection fails because // no providers are available (no API keys configured and DMR not installed). -type ErrAutoModelFallback struct{} +type AutoModelFallbackError struct{} -func (e *ErrAutoModelFallback) Error() string { +func (e *AutoModelFallbackError) Error() string { var hints []string for _, p := range cloudProviders { hints = append(hints, fmt.Sprintf(" - %s: %s", p.name, p.hint)) diff --git a/pkg/config/types/commands.go b/pkg/config/types/commands.go index a383fad0d..75dfa1f2e 100644 --- a/pkg/config/types/commands.go +++ b/pkg/config/types/commands.go @@ -112,7 +112,7 @@ func (c *Commands) UnmarshalYAML(unmarshal func(any) error) error { // parseCommandValue parses a command value which can be either: // - a simple string (becomes the instruction) -// - a map with description/instruction fields +// - a map with description/instruction fields. func parseCommandValue(v any) (Command, error) { switch val := v.(type) { case string: diff --git a/pkg/config/v0/types.go b/pkg/config/v0/types.go index 630ce7989..d0cbf519c 100644 --- a/pkg/config/v0/types.go +++ b/pkg/config/v0/types.go @@ -8,7 +8,7 @@ import ( const Version = "0" -// Toolset represents a tool configuration +// Toolset represents a tool configuration. type Toolset struct { Type string `json:"type,omitempty" yaml:"type,omitempty"` Command string `json:"command,omitempty" yaml:"command,omitempty"` @@ -25,7 +25,7 @@ type Remote struct { Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` } -// Ensure that either Command or Remote is set, but not both empty +// Ensure that either Command or Remote is set, but not both empty. func (t *Toolset) validate() error { if t.Type != "mcp" { return nil diff --git a/pkg/environment/keychain.go b/pkg/environment/keychain.go index 85b529dcc..07c3d9b02 100644 --- a/pkg/environment/keychain.go +++ b/pkg/environment/keychain.go @@ -13,9 +13,9 @@ import ( // via the `security` command-line tool. type KeychainProvider struct{} -type ErrKeychainNotAvailable struct{} +type KeychainNotAvailableError struct{} -func (ErrKeychainNotAvailable) Error() string { +func (KeychainNotAvailableError) Error() string { return "security command is not available (macOS keychain access)" } @@ -27,7 +27,7 @@ func NewKeychainProvider() (*KeychainProvider, error) { slog.Warn("failed to lookup `security` binary", "error", err) } if path == "" { - return nil, ErrKeychainNotAvailable{} + return nil, KeychainNotAvailableError{} } return &KeychainProvider{}, nil } diff --git a/pkg/environment/pass.go b/pkg/environment/pass.go index 892fa4952..cade97fe1 100644 --- a/pkg/environment/pass.go +++ b/pkg/environment/pass.go @@ -13,9 +13,9 @@ import ( // manager. type PassProvider struct{} -type ErrPassNotAvailable struct{} +type PassNotAvailableError struct{} -func (ErrPassNotAvailable) Error() string { +func (PassNotAvailableError) Error() string { return "pass is not installed" } @@ -26,7 +26,7 @@ func NewPassProvider() (*PassProvider, error) { slog.Warn("failed to lookup `pass` binary", "error", err) } if path == "" { - return nil, ErrPassNotAvailable{} + return nil, PassNotAvailableError{} } return &PassProvider{}, nil } diff --git a/pkg/teamloader/teamloader.go b/pkg/teamloader/teamloader.go index b9c648c4d..ea5fe878c 100644 --- a/pkg/teamloader/teamloader.go +++ b/pkg/teamloader/teamloader.go @@ -162,7 +162,7 @@ func LoadWithConfig(ctx context.Context, agentSource config.Source, runConfig *c if err != nil { // Return auto model fallback errors and DMR not installed errors directly // without wrapping to provide cleaner messages - var autoErr *config.ErrAutoModelFallback + var autoErr *config.AutoModelFallbackError if errors.As(err, &autoErr) || errors.Is(err, dmr.ErrNotInstalled) { return nil, err } @@ -314,7 +314,7 @@ func getModelsForAgent(ctx context.Context, cfg *latest.Config, a *latest.AgentC if err != nil { // Return a cleaner error message for auto model selection failures if isAutoModel { - return nil, false, &config.ErrAutoModelFallback{} + return nil, false, &config.AutoModelFallbackError{} } return nil, false, err } diff --git a/pkg/teamloader/teamloader_test.go b/pkg/teamloader/teamloader_test.go index 8e270a89e..e5a0e3a08 100644 --- a/pkg/teamloader/teamloader_test.go +++ b/pkg/teamloader/teamloader_test.go @@ -256,8 +256,8 @@ func TestAutoModelFallbackError(t *testing.T) { _, err = Load(t.Context(), agentSource, runConfig) require.Error(t, err) - var autoErr *config.ErrAutoModelFallback - require.ErrorAs(t, err, &autoErr, "expected ErrAutoModelFallback when auto model selection fails") + var autoErr *config.AutoModelFallbackError + require.ErrorAs(t, err, &autoErr, "expected AutoModelFallbackError when auto model selection fails") } func TestIsThinkingBudgetDisabled(t *testing.T) { From a6c4287c30f5d9ced2acb17ac10ae8fbec69224b Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:29:27 +0100 Subject: [PATCH 09/15] Enable nosprintfhostport linter and fix 3 issues Use net.JoinHostPort instead of fmt.Sprintf for host:port URL construction in DMR fallback URLs. This ensures correct handling of IPv6 addresses. Assisted-By: cagent --- .golangci.yml | 1 + pkg/model/provider/dmr/client.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 8add21c9f..b7949d391 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,6 +37,7 @@ linters: - mirror - nakedret - nolintlint + - nosprintfhostport - nilnesserr - predeclared - reassign diff --git a/pkg/model/provider/dmr/client.go b/pkg/model/provider/dmr/client.go index 7e825adce..d7a8f7346 100644 --- a/pkg/model/provider/dmr/client.go +++ b/pkg/model/provider/dmr/client.go @@ -193,13 +193,13 @@ func getDMRFallbackURLs(containerized bool) []string { // Inside a container: try Docker internal hostnames and bridge gateway return []string{ fmt.Sprintf("http://%s%s/v1/", dmrModelRunnerInternal, dmrInferencePrefix), - fmt.Sprintf("http://%s:%s%s/v1/", dmrHostDockerInternal, dmrDefaultPort, dmrInferencePrefix), - fmt.Sprintf("http://%s:%s%s/v1/", dmrDockerBridgeGateway, dmrDefaultPort, dmrInferencePrefix), + "http://" + net.JoinHostPort(dmrHostDockerInternal, dmrDefaultPort) + dmrInferencePrefix + "/v1/", + "http://" + net.JoinHostPort(dmrDockerBridgeGateway, dmrDefaultPort) + dmrInferencePrefix + "/v1/", } } // On the host: only localhost makes sense as a fallback return []string{ - fmt.Sprintf("http://%s:%s%s/v1/", dmrLocalhost, dmrDefaultPort, dmrInferencePrefix), + "http://" + net.JoinHostPort(dmrLocalhost, dmrDefaultPort) + dmrInferencePrefix + "/v1/", } } From a1a2764f1bafec3416f4f3fd475bffebab01df40 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:30:28 +0100 Subject: [PATCH 10/15] Enable rowserrcheck linter and fix 3 missing rows.Err() checks Add rows.Err() checks after row iteration loops in: - memory/database/sqlite/sqlite.go (GetMemories) - session/migrations.go (GetAppliedMigrations) - session/store.go (GetSessionSummaries) These are correctness bugs: without checking rows.Err(), I/O errors during iteration would be silently ignored. Assisted-By: cagent --- .golangci.yml | 1 + pkg/memory/database/sqlite/sqlite.go | 4 ++++ pkg/session/migrations.go | 4 ++++ pkg/session/store.go | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index b7949d391..58d6449c8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,6 +42,7 @@ linters: - predeclared - reassign - revive + - rowserrcheck - sloglint - staticcheck - testableexamples diff --git a/pkg/memory/database/sqlite/sqlite.go b/pkg/memory/database/sqlite/sqlite.go index 515d0df56..deebaaec7 100644 --- a/pkg/memory/database/sqlite/sqlite.go +++ b/pkg/memory/database/sqlite/sqlite.go @@ -55,6 +55,10 @@ func (m *MemoryDatabase) GetMemories(ctx context.Context) ([]database.UserMemory memories = append(memories, memory) } + if err := rows.Err(); err != nil { + return nil, err + } + return memories, nil } diff --git a/pkg/session/migrations.go b/pkg/session/migrations.go index ab7ec534b..d62c0c415 100644 --- a/pkg/session/migrations.go +++ b/pkg/session/migrations.go @@ -161,6 +161,10 @@ func (m *MigrationManager) GetAppliedMigrations(ctx context.Context) ([]Migratio migrations = append(migrations, migration) } + if err := rows.Err(); err != nil { + return nil, err + } + return migrations, nil } diff --git a/pkg/session/store.go b/pkg/session/store.go index 024829670..1823a96e5 100644 --- a/pkg/session/store.go +++ b/pkg/session/store.go @@ -870,6 +870,10 @@ func (s *SQLiteSessionStore) GetSessionSummaries(ctx context.Context) ([]Summary }) } + if err := rows.Err(); err != nil { + return nil, err + } + return summaries, nil } From 40445e84882f60f68a7cd762abc6acf39d73eec4 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:31:55 +0100 Subject: [PATCH 11/15] Enable gomoddirectives linter Configure replace-allow-list to permit the intentional github.com/charmbracelet/ultraviolet replacement. Assisted-By: cagent --- .golangci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 58d6449c8..f67ecb62d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -22,6 +22,7 @@ linters: - gochecknoinits - gochecksumtype - gocritic + - gomoddirectives - gomodguard - goprintffuncname - govet @@ -92,6 +93,9 @@ linters: deny: - pkg: github.com/stretchr/testify desc: don't use testify in production code + gomoddirectives: + replace-allow-list: + - github.com/charmbracelet/ultraviolet gocritic: disabled-checks: - dupImport From 2723a778a8d3467541a3a0a61222e2bcfeb1addf Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:39:33 +0100 Subject: [PATCH 12/15] Enable recvcheck linter with nolint for Marshal/Unmarshal types recvcheck flags types that mix pointer and value receivers. The 5 flagged types (DeferConfig, RAGStrategyConfig in v2/v3/latest) inherently require mixed receivers: MarshalYAML/MarshalJSON must use value receivers so they're callable on slice elements, while UnmarshalYAML/UnmarshalJSON must use pointer receivers to mutate. Added //nolint:recvcheck with explanatory comments on these types. Assisted-By: cagent --- .golangci.yml | 1 + pkg/config/latest/types.go | 4 ++-- pkg/config/v2/types.go | 2 +- pkg/config/v3/types.go | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index f67ecb62d..a7ccdd932 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -42,6 +42,7 @@ linters: - nilnesserr - predeclared - reassign + - recvcheck - revive - rowserrcheck - sloglint diff --git a/pkg/config/latest/types.go b/pkg/config/latest/types.go index 6f7412ed1..4c6cbb5d8 100644 --- a/pkg/config/latest/types.go +++ b/pkg/config/latest/types.go @@ -484,7 +484,7 @@ type SandboxConfig struct { // DeferConfig represents the deferred loading configuration for a toolset. // It can be either a boolean (true to defer all tools) or a slice of strings // (list of tool names to defer). -type DeferConfig struct { +type DeferConfig struct { //nolint:recvcheck // MarshalYAML must use value receiver for YAML slice encoding, UnmarshalYAML must use pointer // DeferAll is true when all tools should be deferred DeferAll bool `json:"-"` // Tools is the list of specific tool names to defer (empty if DeferAll is true) @@ -635,7 +635,7 @@ func (c *RAGConfig) GetRespectVCS() bool { // RAGStrategyConfig represents a single retrieval strategy configuration // Strategy-specific fields are stored in Params (validated by strategy implementation) -type RAGStrategyConfig struct { +type RAGStrategyConfig struct { //nolint:recvcheck // Marshal methods must use value receiver for YAML/JSON slice encoding, Unmarshal must use pointer Type string `json:"type"` // Strategy type: "chunked-embeddings", "bm25", etc. Docs []string `json:"docs,omitempty"` // Strategy-specific documents (augments shared docs) Database RAGDatabaseConfig `json:"database,omitempty"` // Database configuration diff --git a/pkg/config/v2/types.go b/pkg/config/v2/types.go index d071c3fbf..fb100d361 100644 --- a/pkg/config/v2/types.go +++ b/pkg/config/v2/types.go @@ -251,7 +251,7 @@ type RAGConfig struct { // RAGStrategyConfig represents a single retrieval strategy configuration // Strategy-specific fields are stored in Params (validated by strategy implementation) -type RAGStrategyConfig struct { +type RAGStrategyConfig struct { //nolint:recvcheck // Marshal methods must use value receiver for YAML/JSON slice encoding, Unmarshal must use pointer Type string `json:"type"` // Strategy type: "chunked-embeddings", "bm25", etc. Docs []string `json:"docs,omitempty"` // Strategy-specific documents (augments shared docs) Database RAGDatabaseConfig `json:"database,omitempty"` // Database configuration diff --git a/pkg/config/v3/types.go b/pkg/config/v3/types.go index 88ce4cf6c..54d084c98 100644 --- a/pkg/config/v3/types.go +++ b/pkg/config/v3/types.go @@ -230,7 +230,7 @@ type SandboxConfig struct { // DeferConfig represents the deferred loading configuration for a toolset. // It can be either a boolean (true to defer all tools) or a slice of strings // (list of tool names to defer). -type DeferConfig struct { +type DeferConfig struct { //nolint:recvcheck // MarshalYAML must use value receiver for YAML slice encoding, UnmarshalYAML must use pointer // DeferAll is true when all tools should be deferred DeferAll bool `json:"-"` // Tools is the list of specific tool names to defer (empty if DeferAll is true) @@ -381,7 +381,7 @@ func (c *RAGConfig) GetRespectVCS() bool { // RAGStrategyConfig represents a single retrieval strategy configuration // Strategy-specific fields are stored in Params (validated by strategy implementation) -type RAGStrategyConfig struct { +type RAGStrategyConfig struct { //nolint:recvcheck // Marshal methods must use value receiver for YAML/JSON slice encoding, Unmarshal must use pointer Type string `json:"type"` // Strategy type: "chunked-embeddings", "bm25", etc. Docs []string `json:"docs,omitempty"` // Strategy-specific documents (augments shared docs) Database RAGDatabaseConfig `json:"database,omitempty"` // Database configuration From d296164c8bdc49b08ab1a9f6cac004f8f9d98b6e Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:49:49 +0100 Subject: [PATCH 13/15] Enable nilerr linter with nolint for 11 intentional cases All 11 cases are deliberate patterns where err is checked but nil is returned: - Context cancellation treated as normal completion (acp, fake, mcp) - Fallback to partial/default result on non-critical error (html, fsx) - WalkDir callbacks skipping unreadable entries (skills, filesystem) - Script errors returned as data, not Go errors (codemode) - Non-JSON output skipping toon encoding (teamloader) Assisted-By: cagent --- .golangci.yml | 1 + pkg/acp/agent.go | 2 +- pkg/app/export/html.go | 2 +- pkg/fake/proxy.go | 2 +- pkg/fsx/fs.go | 2 +- pkg/skills/skills.go | 2 +- pkg/teamloader/toon.go | 2 +- pkg/tools/builtin/filesystem.go | 6 +++--- pkg/tools/codemode/exec.go | 2 +- pkg/tools/mcp/mcp.go | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index a7ccdd932..6fdd878d3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -37,6 +37,7 @@ linters: - misspell - mirror - nakedret + - nilerr - nolintlint - nosprintfhostport - nilnesserr diff --git a/pkg/acp/agent.go b/pkg/acp/agent.go index f747b21d3..ee385cfdf 100644 --- a/pkg/acp/agent.go +++ b/pkg/acp/agent.go @@ -248,7 +248,7 @@ func (a *Agent) Prompt(ctx context.Context, params acp.PromptRequest) (acp.Promp // Run the agent and stream updates if err := a.runAgent(turnCtx, acpSess); err != nil { if turnCtx.Err() != nil { - return acp.PromptResponse{StopReason: acp.StopReasonCancelled}, nil + return acp.PromptResponse{StopReason: acp.StopReasonCancelled}, nil //nolint:nilerr // context cancellation is not an error, return cancelled status } return acp.PromptResponse{}, err } diff --git a/pkg/app/export/html.go b/pkg/app/export/html.go index 19ecfd1cd..f569f5263 100644 --- a/pkg/app/export/html.go +++ b/pkg/app/export/html.go @@ -155,7 +155,7 @@ func ToFile(data SessionData, filename string) (string, error) { absPath, err := filepath.Abs(filename) if err != nil { - return filename, nil + return filename, nil //nolint:nilerr // fall back to relative path if Abs fails } return absPath, nil } diff --git a/pkg/fake/proxy.go b/pkg/fake/proxy.go index 46c138e32..7404ded24 100644 --- a/pkg/fake/proxy.go +++ b/pkg/fake/proxy.go @@ -429,7 +429,7 @@ func StreamCopy(c echo.Context, resp *http.Response) error { if result.err != nil { // io.EOF or context canceled means normal completion if result.err == io.EOF || ctx.Err() != nil { - return nil + return nil //nolint:nilerr // EOF and context cancellation are normal stream termination } slog.ErrorContext(ctx, "stream read error", "error", result.err) return result.err diff --git a/pkg/fsx/fs.go b/pkg/fsx/fs.go index 34d5d20bc..6673420be 100644 --- a/pkg/fsx/fs.go +++ b/pkg/fsx/fs.go @@ -42,7 +42,7 @@ func directoryTree(path string, isPathAllowed func(string) error, shouldIgnore f entries, err := os.ReadDir(path) if err != nil { - return node, nil // Return partial result on error + return node, nil //nolint:nilerr // return partial tree on ReadDir failure } for _, entry := range entries { diff --git a/pkg/skills/skills.go b/pkg/skills/skills.go index 9bdcf50e9..3ec8fc84c 100644 --- a/pkg/skills/skills.go +++ b/pkg/skills/skills.go @@ -218,7 +218,7 @@ func loadSkillsRecursive(dir string) []Skill { _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { - return nil + return nil //nolint:nilerr // skip unreadable entries } if isHiddenOrSymlink(d) || d.Name() != skillFile { return nil diff --git a/pkg/teamloader/toon.go b/pkg/teamloader/toon.go index 3cb333f89..770ca70cd 100644 --- a/pkg/teamloader/toon.go +++ b/pkg/teamloader/toon.go @@ -38,7 +38,7 @@ func (f *toonTools) Tools(ctx context.Context) ([]tools.Tool, error) { var o map[string]any err = json.Unmarshal([]byte(res.Output), &o) if err != nil { - return res, nil + return res, nil //nolint:nilerr // output is not JSON, skip toon encoding } tooned, err := gotoon.Encode(o) diff --git a/pkg/tools/builtin/filesystem.go b/pkg/tools/builtin/filesystem.go index 9581243ae..5591b4237 100644 --- a/pkg/tools/builtin/filesystem.go +++ b/pkg/tools/builtin/filesystem.go @@ -592,7 +592,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search err := filepath.WalkDir(resolvedPath, func(path string, d fs.DirEntry, err error) error { if err != nil { - return nil + return nil //nolint:nilerr // skip unreadable entries } // Check VCS ignore rules @@ -606,7 +606,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search // Check exclude patterns against relative path from search root relPath, err := filepath.Rel(resolvedPath, path) if err != nil { - return nil + return nil //nolint:nilerr // skip paths that can't be made relative } for _, exclude := range args.ExcludePatterns { @@ -625,7 +625,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search content, err := os.ReadFile(path) if err != nil { - return nil + return nil //nolint:nilerr // skip unreadable files } lines := strings.Split(string(content), "\n") diff --git a/pkg/tools/codemode/exec.go b/pkg/tools/codemode/exec.go index 62ebe3cbe..25144323d 100644 --- a/pkg/tools/codemode/exec.go +++ b/pkg/tools/codemode/exec.go @@ -66,7 +66,7 @@ func (c *codeModeTool) runJavascript(ctx context.Context, script string) (Script v, err := vm.RunString(script) if err != nil { // Script execution failed - include tool call history to help LLM understand what went wrong - return ScriptResult{ + return ScriptResult{ //nolint:nilerr // script errors are returned as data, not Go errors StdOut: stdOut.String(), StdErr: stdErr.String(), Value: err.Error(), diff --git a/pkg/tools/mcp/mcp.go b/pkg/tools/mcp/mcp.go index 4596d9e60..4ee00fe25 100644 --- a/pkg/tools/mcp/mcp.go +++ b/pkg/tools/mcp/mcp.go @@ -243,7 +243,7 @@ func (ts *Toolset) Stop(ctx context.Context) error { if err := ts.mcpClient.Close(context.WithoutCancel(ctx)); err != nil { if ctx.Err() != nil { - return nil + return nil //nolint:nilerr // close error expected when context already cancelled } slog.Error("Failed to stop MCP toolset", "server", ts.logID, "error", err) return err From 3ca260c4579712f51dc89e2a4a258f3e3c2c0ebe Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:54:28 +0100 Subject: [PATCH 14/15] Enable errorlint linter and fix 18 issues Fix three categories of error handling issues: - Use errors.Is instead of == for error comparison (6 fixes) io.EOF, sql.ErrNoRows, http.ErrServerClosed, context.Canceled - Use errors.As instead of type assertion on errors (4 fixes) *exec.ExitError, *environment.RequiredEnvError, cli.RuntimeError - Use %w instead of %v in fmt.Errorf for proper wrapping (8 fixes) api.go, eval.go, store.go, sqliteutil.go Assisted-By: cagent --- .golangci.yml | 1 + cmd/root/run.go | 4 +++- pkg/config/config_test.go | 4 +++- pkg/evaluation/eval.go | 2 +- pkg/fake/proxy.go | 3 ++- pkg/fsx/fs.go | 3 ++- pkg/hooks/executor.go | 4 +++- pkg/mcp/server.go | 3 ++- pkg/model/provider/custom_provider_test.go | 3 ++- pkg/rag/strategy/bm25_database.go | 3 ++- pkg/runtime/connectrpc_client.go | 3 ++- pkg/session/migrations.go | 3 ++- pkg/session/store.go | 2 +- pkg/sqliteutil/sqlite.go | 2 +- pkg/tools/builtin/api.go | 8 ++++---- pkg/tools/builtin/shell.go | 4 +++- 16 files changed, 34 insertions(+), 18 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6fdd878d3..4841f0870 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,6 +14,7 @@ linters: - durationcheck - errcheck - errname + - errorlint - exptostd - fatcontext - forbidigo diff --git a/cmd/root/run.go b/cmd/root/run.go index 552d330ff..1f2ae169a 100644 --- a/cmd/root/run.go +++ b/cmd/root/run.go @@ -2,6 +2,7 @@ package root import ( "context" + "errors" "fmt" "io" "log/slog" @@ -422,7 +423,8 @@ func (f *runExecFlags) handleExecMode(ctx context.Context, out *cli.Printer, rt OutputJSON: f.outputJSON, AutoApprove: f.autoApprove, }, rt, sess, execArgs) - if cliErr, ok := err.(cli.RuntimeError); ok { + var cliErr cli.RuntimeError + if errors.As(err, &cliErr) { return RuntimeError{Err: cliErr.Err} } return err diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 5b339360a..44ccbd2c6 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -271,7 +271,9 @@ func TestCheckRequiredEnvVars(t *testing.T) { require.NoError(t, err) } else { require.Error(t, err) - assert.Equal(t, test.expectedMissing, err.(*environment.RequiredEnvError).Missing) + var reqErr *environment.RequiredEnvError + require.ErrorAs(t, err, &reqErr) + assert.Equal(t, test.expectedMissing, reqErr.Missing) } }) } diff --git a/pkg/evaluation/eval.go b/pkg/evaluation/eval.go index f060a39fb..d5ad81478 100644 --- a/pkg/evaluation/eval.go +++ b/pkg/evaluation/eval.go @@ -272,7 +272,7 @@ func (r *Runner) preBuildImages(ctx context.Context, out io.Writer, evals []Inpu } if len(errs) > 0 { - return fmt.Errorf("failed to build %d image(s): %v", len(errs), errs[0]) + return fmt.Errorf("failed to build %d image(s): %w", len(errs), errs[0]) } return nil diff --git a/pkg/fake/proxy.go b/pkg/fake/proxy.go index 7404ded24..24e053efb 100644 --- a/pkg/fake/proxy.go +++ b/pkg/fake/proxy.go @@ -6,6 +6,7 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" "io" "log/slog" @@ -428,7 +429,7 @@ func StreamCopy(c echo.Context, resp *http.Response) error { } if result.err != nil { // io.EOF or context canceled means normal completion - if result.err == io.EOF || ctx.Err() != nil { + if errors.Is(result.err, io.EOF) || ctx.Err() != nil { return nil //nolint:nilerr // EOF and context cancellation are normal stream termination } slog.ErrorContext(ctx, "stream read error", "error", result.err) diff --git a/pkg/fsx/fs.go b/pkg/fsx/fs.go index 6673420be..92e2fe5a0 100644 --- a/pkg/fsx/fs.go +++ b/pkg/fsx/fs.go @@ -2,6 +2,7 @@ package fsx import ( "context" + "errors" "io/fs" "os" "path/filepath" @@ -209,7 +210,7 @@ func WalkFiles(ctx context.Context, root string, opts WalkFilesOptions) ([]strin return nil }) - if err != nil && err != context.Canceled && err != context.DeadlineExceeded { + if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { return files, err } diff --git a/pkg/hooks/executor.go b/pkg/hooks/executor.go index 84876977f..7d86c2027 100644 --- a/pkg/hooks/executor.go +++ b/pkg/hooks/executor.go @@ -5,6 +5,7 @@ import ( "cmp" "context" "encoding/json" + "errors" "fmt" "log/slog" "os" @@ -264,7 +265,8 @@ func (e *Executor) executeHook(ctx context.Context, hook Hook, inputJSON []byte) exitCode := 0 if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { exitCode = exitErr.ExitCode() } else { return nil, stdout.String(), stderr.String(), -1, err diff --git a/pkg/mcp/server.go b/pkg/mcp/server.go index fbd51f637..3e60254fa 100644 --- a/pkg/mcp/server.go +++ b/pkg/mcp/server.go @@ -3,6 +3,7 @@ package mcp import ( "cmp" "context" + "errors" "fmt" "log/slog" "net" @@ -74,7 +75,7 @@ func StartHTTPServer(ctx context.Context, agentFilename, agentName string, runCo case <-ctx.Done(): return httpServer.Shutdown(context.Background()) case err := <-errCh: - if err == http.ErrServerClosed { + if errors.Is(err, http.ErrServerClosed) { return nil } return err diff --git a/pkg/model/provider/custom_provider_test.go b/pkg/model/provider/custom_provider_test.go index 17bc1facc..557f16e73 100644 --- a/pkg/model/provider/custom_provider_test.go +++ b/pkg/model/provider/custom_provider_test.go @@ -3,6 +3,7 @@ package provider import ( "context" "encoding/json" + "errors" "io" "net/http" "net/http/httptest" @@ -502,7 +503,7 @@ func drainStream(t *testing.T, stream chat.MessageStream) { t.Helper() for { _, err := stream.Recv() - if err == io.EOF { + if errors.Is(err, io.EOF) { return } if err != nil { diff --git a/pkg/rag/strategy/bm25_database.go b/pkg/rag/strategy/bm25_database.go index 8f142309a..7e70f012d 100644 --- a/pkg/rag/strategy/bm25_database.go +++ b/pkg/rag/strategy/bm25_database.go @@ -3,6 +3,7 @@ package strategy import ( "context" "database/sql" + "errors" "fmt" "log/slog" "os" @@ -96,7 +97,7 @@ func (d *bm25DB) AddDocument(ctx context.Context, doc database.Document) error { fmt.Sprintf("SELECT 1 FROM %s WHERE source_path = ? AND chunk_index = ?", d.docsTable), doc.SourcePath, doc.ChunkIndex).Scan(&exists) - if err != nil && err != sql.ErrNoRows { + if err != nil && !errors.Is(err, sql.ErrNoRows) { return fmt.Errorf("failed to check existing document: %w", err) } diff --git a/pkg/runtime/connectrpc_client.go b/pkg/runtime/connectrpc_client.go index a8f1a226a..27610b142 100644 --- a/pkg/runtime/connectrpc_client.go +++ b/pkg/runtime/connectrpc_client.go @@ -3,6 +3,7 @@ package runtime import ( "context" "encoding/json" + "errors" "fmt" "io" "log/slog" @@ -241,7 +242,7 @@ func (c *ConnectRPCClient) runAgentWithAgentName(ctx context.Context, sessionID, } } - if err := stream.Err(); err != nil && err != io.EOF { + if err := stream.Err(); err != nil && !errors.Is(err, io.EOF) { slog.Error("Stream error", "error", err) eventChan <- Error(fmt.Sprintf("stream error: %v", err)) } diff --git a/pkg/session/migrations.go b/pkg/session/migrations.go index d62c0c415..5aa5c29d2 100644 --- a/pkg/session/migrations.go +++ b/pkg/session/migrations.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "errors" "fmt" "log/slog" "time" @@ -410,7 +411,7 @@ func migrateItem(ctx context.Context, db *sql.DB, sessionID string, position int var exists int err := db.QueryRowContext(ctx, "SELECT 1 FROM sessions WHERE id = ?", subSessionID).Scan(&exists) switch { - case err == sql.ErrNoRows: + case errors.Is(err, sql.ErrNoRows): // Create the sub-session subMessagesJSON, jsonErr := json.Marshal(item.SubSession.Messages) if jsonErr != nil { diff --git a/pkg/session/store.go b/pkg/session/store.go index 1823a96e5..d022fe3be 100644 --- a/pkg/session/store.go +++ b/pkg/session/store.go @@ -375,7 +375,7 @@ func NewSQLiteSessionStore(path string) (Store, error) { if backupErr != nil { // Return the original error if backup failed slog.Error("Failed to backup database for recovery", "error", backupErr) - return nil, fmt.Errorf("migration failed: %w (backup also failed: %v)", err, backupErr) + return nil, fmt.Errorf("migration failed: %w (backup also failed: %w)", err, backupErr) } // Try again with a fresh database diff --git a/pkg/sqliteutil/sqlite.go b/pkg/sqliteutil/sqlite.go index ed4108cc6..90616fbd3 100644 --- a/pkg/sqliteutil/sqlite.go +++ b/pkg/sqliteutil/sqlite.go @@ -88,5 +88,5 @@ func DiagnoseDBOpenError(path string, originalErr error) error { return fmt.Errorf("cannot create database at %q: %q is not a directory", path, dir) } - return fmt.Errorf("cannot create database at %q: permission denied or file cannot be created in %q (original error: %v)", path, dir, originalErr) + return fmt.Errorf("cannot create database at %q: permission denied or file cannot be created in %q (original error: %w)", path, dir, originalErr) } diff --git a/pkg/tools/builtin/api.go b/pkg/tools/builtin/api.go index b8ce16a04..b8ee47711 100644 --- a/pkg/tools/builtin/api.go +++ b/pkg/tools/builtin/api.go @@ -56,14 +56,14 @@ func (t *APITool) callTool(ctx context.Context, toolCall tools.ToolCall) (*tools } jsonData, err := json.Marshal(params) if err != nil { - return nil, fmt.Errorf("failed to marshal request body: %v", err) + return nil, fmt.Errorf("failed to marshal request body: %w", err) } reqBody = bytes.NewReader(jsonData) } req, err := http.NewRequestWithContext(ctx, t.config.Method, endpoint, reqBody) if err != nil { - return nil, fmt.Errorf("failed to create request: %v", err) + return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Set("User-Agent", useragent.Header) @@ -77,14 +77,14 @@ func (t *APITool) callTool(ctx context.Context, toolCall tools.ToolCall) (*tools resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("request failed: %v", err) + return nil, fmt.Errorf("request failed: %w", err) } defer resp.Body.Close() maxSize := int64(1 << 20) body, err := io.ReadAll(io.LimitReader(resp.Body, maxSize)) if err != nil { - return nil, fmt.Errorf("failed to read response body: %v", err) + return nil, fmt.Errorf("failed to read response body: %w", err) } return tools.ResultSuccess(limitOutput(string(body))), nil diff --git a/pkg/tools/builtin/shell.go b/pkg/tools/builtin/shell.go index 5e9609112..88735a843 100644 --- a/pkg/tools/builtin/shell.go +++ b/pkg/tools/builtin/shell.go @@ -4,6 +4,7 @@ import ( "bytes" "cmp" "context" + "errors" "fmt" "os" "os/exec" @@ -242,7 +243,8 @@ func (h *shellHandler) monitorJob(job *backgroundJob, cmd *exec.Cmd) { } if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { job.exitCode = exitErr.ExitCode() } else { job.exitCode = -1 From d764543446e8f74b892a8a812bf581450b1bdcb5 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 6 Feb 2026 17:58:31 +0100 Subject: [PATCH 15/15] Revert nilerr linter: too many //nolint annotations All 11 issues were intentional patterns, requiring 11 //nolint directives with no actual code improvement. Assisted-By: cagent --- .golangci.yml | 1 - pkg/acp/agent.go | 2 +- pkg/app/export/html.go | 2 +- pkg/fake/proxy.go | 2 +- pkg/fsx/fs.go | 2 +- pkg/skills/skills.go | 2 +- pkg/teamloader/toon.go | 2 +- pkg/tools/builtin/filesystem.go | 6 +++--- pkg/tools/codemode/exec.go | 2 +- pkg/tools/mcp/mcp.go | 2 +- 10 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 4841f0870..4b463cc9d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -38,7 +38,6 @@ linters: - misspell - mirror - nakedret - - nilerr - nolintlint - nosprintfhostport - nilnesserr diff --git a/pkg/acp/agent.go b/pkg/acp/agent.go index ee385cfdf..f747b21d3 100644 --- a/pkg/acp/agent.go +++ b/pkg/acp/agent.go @@ -248,7 +248,7 @@ func (a *Agent) Prompt(ctx context.Context, params acp.PromptRequest) (acp.Promp // Run the agent and stream updates if err := a.runAgent(turnCtx, acpSess); err != nil { if turnCtx.Err() != nil { - return acp.PromptResponse{StopReason: acp.StopReasonCancelled}, nil //nolint:nilerr // context cancellation is not an error, return cancelled status + return acp.PromptResponse{StopReason: acp.StopReasonCancelled}, nil } return acp.PromptResponse{}, err } diff --git a/pkg/app/export/html.go b/pkg/app/export/html.go index f569f5263..19ecfd1cd 100644 --- a/pkg/app/export/html.go +++ b/pkg/app/export/html.go @@ -155,7 +155,7 @@ func ToFile(data SessionData, filename string) (string, error) { absPath, err := filepath.Abs(filename) if err != nil { - return filename, nil //nolint:nilerr // fall back to relative path if Abs fails + return filename, nil } return absPath, nil } diff --git a/pkg/fake/proxy.go b/pkg/fake/proxy.go index 24e053efb..ab0d872f4 100644 --- a/pkg/fake/proxy.go +++ b/pkg/fake/proxy.go @@ -430,7 +430,7 @@ func StreamCopy(c echo.Context, resp *http.Response) error { if result.err != nil { // io.EOF or context canceled means normal completion if errors.Is(result.err, io.EOF) || ctx.Err() != nil { - return nil //nolint:nilerr // EOF and context cancellation are normal stream termination + return nil } slog.ErrorContext(ctx, "stream read error", "error", result.err) return result.err diff --git a/pkg/fsx/fs.go b/pkg/fsx/fs.go index 92e2fe5a0..ed9a83c73 100644 --- a/pkg/fsx/fs.go +++ b/pkg/fsx/fs.go @@ -43,7 +43,7 @@ func directoryTree(path string, isPathAllowed func(string) error, shouldIgnore f entries, err := os.ReadDir(path) if err != nil { - return node, nil //nolint:nilerr // return partial tree on ReadDir failure + return node, nil // Return partial result on error } for _, entry := range entries { diff --git a/pkg/skills/skills.go b/pkg/skills/skills.go index 3ec8fc84c..9bdcf50e9 100644 --- a/pkg/skills/skills.go +++ b/pkg/skills/skills.go @@ -218,7 +218,7 @@ func loadSkillsRecursive(dir string) []Skill { _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { - return nil //nolint:nilerr // skip unreadable entries + return nil } if isHiddenOrSymlink(d) || d.Name() != skillFile { return nil diff --git a/pkg/teamloader/toon.go b/pkg/teamloader/toon.go index 770ca70cd..3cb333f89 100644 --- a/pkg/teamloader/toon.go +++ b/pkg/teamloader/toon.go @@ -38,7 +38,7 @@ func (f *toonTools) Tools(ctx context.Context) ([]tools.Tool, error) { var o map[string]any err = json.Unmarshal([]byte(res.Output), &o) if err != nil { - return res, nil //nolint:nilerr // output is not JSON, skip toon encoding + return res, nil } tooned, err := gotoon.Encode(o) diff --git a/pkg/tools/builtin/filesystem.go b/pkg/tools/builtin/filesystem.go index 5591b4237..9581243ae 100644 --- a/pkg/tools/builtin/filesystem.go +++ b/pkg/tools/builtin/filesystem.go @@ -592,7 +592,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search err := filepath.WalkDir(resolvedPath, func(path string, d fs.DirEntry, err error) error { if err != nil { - return nil //nolint:nilerr // skip unreadable entries + return nil } // Check VCS ignore rules @@ -606,7 +606,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search // Check exclude patterns against relative path from search root relPath, err := filepath.Rel(resolvedPath, path) if err != nil { - return nil //nolint:nilerr // skip paths that can't be made relative + return nil } for _, exclude := range args.ExcludePatterns { @@ -625,7 +625,7 @@ func (t *FilesystemTool) handleSearchFilesContent(_ context.Context, args Search content, err := os.ReadFile(path) if err != nil { - return nil //nolint:nilerr // skip unreadable files + return nil } lines := strings.Split(string(content), "\n") diff --git a/pkg/tools/codemode/exec.go b/pkg/tools/codemode/exec.go index 25144323d..62ebe3cbe 100644 --- a/pkg/tools/codemode/exec.go +++ b/pkg/tools/codemode/exec.go @@ -66,7 +66,7 @@ func (c *codeModeTool) runJavascript(ctx context.Context, script string) (Script v, err := vm.RunString(script) if err != nil { // Script execution failed - include tool call history to help LLM understand what went wrong - return ScriptResult{ //nolint:nilerr // script errors are returned as data, not Go errors + return ScriptResult{ StdOut: stdOut.String(), StdErr: stdErr.String(), Value: err.Error(), diff --git a/pkg/tools/mcp/mcp.go b/pkg/tools/mcp/mcp.go index 4ee00fe25..4596d9e60 100644 --- a/pkg/tools/mcp/mcp.go +++ b/pkg/tools/mcp/mcp.go @@ -243,7 +243,7 @@ func (ts *Toolset) Stop(ctx context.Context) error { if err := ts.mcpClient.Close(context.WithoutCancel(ctx)); err != nil { if ctx.Err() != nil { - return nil //nolint:nilerr // close error expected when context already cancelled + return nil } slog.Error("Failed to stop MCP toolset", "server", ts.logID, "error", err) return err