refactor: replace direct error comparisons with errors.Is

Improves correctness when working with wrapped errors
This commit is contained in:
Ruslan Semagin 2026-02-19 07:51:46 +03:00
parent e8afd31b28
commit 4e68cf5b1e
10 changed files with 21 additions and 15 deletions

View file

@ -7,6 +7,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -104,7 +105,7 @@ func (c *LINEChannel) Start(ctx context.Context) error {
"addr": addr, "addr": addr,
"path": path, "path": path,
}) })
if err := c.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := c.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
logger.ErrorCF("line", "Webhook server error", map[string]interface{}{ logger.ErrorCF("line", "Webhook server error", map[string]interface{}{
"error": err.Error(), "error": err.Error(),
}) })

View file

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
@ -67,7 +68,7 @@ func (p *CodexCliProvider) Chat(ctx context.Context, messages []Message, tools [
} }
if err != nil { if err != nil {
if ctx.Err() == context.Canceled { if errors.Is(ctx.Err(), context.Canceled) {
return nil, ctx.Err() return nil, ctx.Err()
} }
if stderrStr := stderr.String(); stderrStr != "" { if stderrStr := stderr.String(); stderrStr != "" {

View file

@ -91,8 +91,8 @@ func (p *CodexProvider) Chat(ctx context.Context, messages []Message, tools []To
if evt.Type == "response.completed" || evt.Type == "response.failed" || evt.Type == "response.incomplete" { if evt.Type == "response.completed" || evt.Type == "response.failed" || evt.Type == "response.incomplete" {
evtResp := evt.Response evtResp := evt.Response
if evtResp.ID != "" { if evtResp.ID != "" {
copy := evtResp cp := evtResp
resp = &copy resp = &cp
} }
} }
} }

View file

@ -2,6 +2,7 @@ package providers
import ( import (
"context" "context"
"errors"
"regexp" "regexp"
"strings" "strings"
) )
@ -102,12 +103,12 @@ func ClassifyError(err error, provider, model string) *FailoverError {
} }
// Context cancellation: user abort, never fallback. // Context cancellation: user abort, never fallback.
if err == context.Canceled { if errors.Is(err, context.Canceled) {
return nil return nil
} }
// Context deadline exceeded: treat as timeout, always fallback. // Context deadline exceeded: treat as timeout, always fallback.
if err == context.DeadlineExceeded { if errors.Is(err, context.DeadlineExceeded) {
return &FailoverError{ return &FailoverError{
Reason: FailoverTimeout, Reason: FailoverTimeout,
Provider: provider, Provider: provider,

View file

@ -293,7 +293,7 @@ func TestFailoverError_ErrorString(t *testing.T) {
func TestFailoverError_Unwrap(t *testing.T) { func TestFailoverError_Unwrap(t *testing.T) {
inner := errors.New("inner error") inner := errors.New("inner error")
fe := &FailoverError{Reason: FailoverTimeout, Wrapped: inner} fe := &FailoverError{Reason: FailoverTimeout, Wrapped: inner}
if fe.Unwrap() != inner { if !errors.Is(inner, fe.Unwrap()) {
t.Error("Unwrap should return wrapped error") t.Error("Unwrap should return wrapped error")
} }
} }

View file

@ -2,6 +2,7 @@ package providers
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -98,7 +99,7 @@ func (fc *FallbackChain) Execute(
for i, candidate := range candidates { for i, candidate := range candidates {
// Check context before each attempt. // Check context before each attempt.
if ctx.Err() == context.Canceled { if errors.Is(ctx.Err(), context.Canceled) {
return nil, context.Canceled return nil, context.Canceled
} }
@ -130,7 +131,7 @@ func (fc *FallbackChain) Execute(
} }
// Context cancellation: abort immediately, no fallback. // Context cancellation: abort immediately, no fallback.
if ctx.Err() == context.Canceled { if errors.Is(ctx.Err(), context.Canceled) {
result.Attempts = append(result.Attempts, FallbackAttempt{ result.Attempts = append(result.Attempts, FallbackAttempt{
Provider: candidate.Provider, Provider: candidate.Provider,
Model: candidate.Model, Model: candidate.Model,
@ -204,7 +205,7 @@ func (fc *FallbackChain) ExecuteImage(
} }
for i, candidate := range candidates { for i, candidate := range candidates {
if ctx.Err() == context.Canceled { if errors.Is(ctx.Err(), context.Canceled) {
return nil, context.Canceled return nil, context.Canceled
} }
@ -219,7 +220,7 @@ func (fc *FallbackChain) ExecuteImage(
return result, nil return result, nil
} }
if ctx.Err() == context.Canceled { if errors.Is(ctx.Err(), context.Canceled) {
result.Attempts = append(result.Attempts, FallbackAttempt{ result.Attempts = append(result.Attempts, FallbackAttempt{
Provider: candidate.Provider, Provider: candidate.Provider,
Model: candidate.Model, Model: candidate.Model,

View file

@ -122,7 +122,7 @@ func TestFallback_ContextCanceled(t *testing.T) {
} }
_, err := fc.Execute(ctx, candidates, run) _, err := fc.Execute(ctx, candidates, run)
if err != context.Canceled { if !errors.Is(err, context.Canceled) {
t.Errorf("expected context.Canceled, got %v", err) t.Errorf("expected context.Canceled, got %v", err)
} }
} }

View file

@ -2,6 +2,7 @@ package tools
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"syscall" "syscall"
"unsafe" "unsafe"
@ -113,7 +114,7 @@ func (t *I2CTool) scan(args map[string]interface{}) *ToolResult {
// Set slave address — EBUSY means a kernel driver owns this address // Set slave address — EBUSY means a kernel driver owns this address
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr)) _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), i2cSlave, uintptr(addr))
if errno != 0 { if errno != 0 {
if errno == syscall.EBUSY { if errors.Is(errno, syscall.EBUSY) {
found = append(found, deviceEntry{ found = append(found, deviceEntry{
Address: fmt.Sprintf("0x%02x", addr), Address: fmt.Sprintf("0x%02x", addr),
Status: "busy (in use by kernel driver)", Status: "busy (in use by kernel driver)",

View file

@ -126,7 +126,7 @@ func TestMessageTool_Execute_SendFailure(t *testing.T) {
if result.Err == nil { if result.Err == nil {
t.Error("Expected Err to be set") t.Error("Expected Err to be set")
} }
if result.Err != sendErr { if !errors.Is(sendErr, result.Err) {
t.Errorf("Expected Err to be sendErr, got %v", result.Err) t.Errorf("Expected Err to be sendErr, got %v", result.Err)
} }
} }

View file

@ -3,6 +3,7 @@ package tools
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
@ -188,7 +189,7 @@ func (t *ExecTool) Execute(ctx context.Context, args map[string]interface{}) *To
} }
if err != nil { if err != nil {
if cmdCtx.Err() == context.DeadlineExceeded { if errors.Is(cmdCtx.Err(), context.DeadlineExceeded) {
msg := fmt.Sprintf("Command timed out after %v", t.timeout) msg := fmt.Sprintf("Command timed out after %v", t.timeout)
return &ToolResult{ return &ToolResult{
ForLLM: msg, ForLLM: msg,