360 Secure SkillHub
360 安全技能中心

Go Error Handler

该 Skill 针对 Go 代码库做错误处理审查,覆盖吞错、包装缺失、哨兵与自定义错误、类型断言、HTTP 与 goroutine 错误及测试覆盖,并输出按优先级整理的 Markdown 报告。

charlie-morrison
charlie-morrison
热度 27.02
最近更新 2026-05-10 17:59:32
分类:开发依赖:代码执行 / 读写文件 / 内部调用大模型应用场景:代码审查

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add go-error-handler
Metadata
name
go-error-handler
description
Analyze Go error handling patterns — detect swallowed errors, missing error wrapping, sentinel vs custom error usage, type assertions, and idiomatic error propagation. Use when reviewing Go codebases for error handling quality or enforcing Go error conventions.
metadata
{"tags":["go","golang","error-handling","code-quality","best-practices"]}

Go Error Handler Analyzer

Deep analysis of error handling patterns in Go codebases. Detects swallowed errors, missing wrapping context, incorrect sentinel usage, unsafe type assertions, and non-idiomatic patterns. Produces prioritized findings aligned with Go proverbs and stdlib conventions.

Use when: reviewing Go code for production readiness, auditing error handling, or establishing team conventions.

Analysis Steps

1. Project Discovery

cat go.mod 2>/dev/null | head -10
find . -name "*.go" -not -path '*/vendor/*' | wc -l
find . -type d -name "errors" -o -name "errs" -not -path '*/vendor/*' 2>/dev/null
grep "pkg/errors\|go.uber.org/multierr\|cockroachdb/errors" go.mod 2>/dev/null

Determine: Go version (affects errors.Is/As availability), error library usage, custom error infrastructure.

2. Swallowed Errors

Most critical check. A swallowed error is an err return value that is ignored.

# Explicit underscore ignoring error
grep -rn '\b_\s*=.*(' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -25

# err assigned but never checked
grep -rn 'err\s*=' --include="*.go" . 2>/dev/null | grep -v 'if.*err\|return.*err\|vendor/' | head -20

# defer with ignored error (defer f.Close())
grep -rn 'defer.*Close()\|defer.*Flush()\|defer.*Rollback()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# http.Error without return (handler continues after error response)
grep -A2 'http\.Error(' --include="*.go" . 2>/dev/null | grep -v 'return\|vendor/' | head -15

Severity guide:

  • Critical: ignored errors from DB, file I/O, network calls
  • High: _ = someFunc() on functions that can fail meaningfully
  • Medium: defer f.Close() without error handling — use named return + defer closure
  • Low: ignoring fmt.Fprintf to stdout (acceptable in CLIs)

3. Error Wrapping Analysis

# Proper wrapping with %w (Go 1.13+)
grep -rn 'fmt\.Errorf.*%w' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# %v/%s instead of %w (loses error chain)
grep -rn 'fmt\.Errorf.*%[vs].*err\b' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# Bare return of error without wrapping (loses context)
grep -rn 'return.*err$' --include="*.go" . 2>/dev/null | grep -v 'nil\|vendor/\|_test.go' | head -20

# Error message conventions (should not start with capital or end with punctuation)
grep -rn 'fmt\.Errorf("[ ]*[A-Z]' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10
grep -rn 'errors\.New(".*\.")' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

Flag:

  • %v instead of %w: fmt.Errorf("failed: %v", err) breaks errors.Is()/errors.As() — use %w
  • Bare error return: return err across package boundaries loses call-site context — wrap with fmt.Errorf("operation: %w", err)
  • Capitalized/punctuated error messages: Go convention is lowercase, no trailing period (errors compose)

4. Sentinel & Custom Error Patterns

# Sentinel errors
grep -rn 'var\s\+Err[A-Z].*=\s*errors\.New' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# String comparison instead of errors.Is (fragile)
grep -rn 'err\.Error()\s*==' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

# Direct equality (breaks with wrapping)
grep -rn 'err\s*==\s*Err\|err\s*!=\s*Err' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

# errors.Is / errors.As usage (correct patterns)
grep -rn 'errors\.Is(\|errors\.As(' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# Custom error types
grep -rn 'func.*Error()\s*string' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -15

# Type assertion on errors (fragile with wrapping)
grep -rn 'err\.(\*' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

Flag:

  • String comparison: err.Error() == "not found" — use errors.Is(err, ErrNotFound)
  • Direct equality: err == ErrNotFound fails if wrapped — use errors.Is()
  • Type assertion: err.(*MyError) fails on wrapped errors — use errors.As()
  • Missing Unwrap: custom error types wrapping other errors must implement Unwrap() error
  • Repeated string errors: errors.New("not found") in multiple places should be a sentinel

5. HTTP Handler & Goroutine Errors

# Internal errors leaked to client (security risk)
grep -rn 'http\.Error(w,.*err\.Error()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

# Panic in non-test code
grep -rn 'panic(' --include="*.go" . 2>/dev/null | grep -v 'vendor/\|_test.go' | head -10

# Goroutines without error propagation
grep -B2 -A10 'go func()' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -30

# errgroup usage
grep -rn 'errgroup\|g\.Go\|group\.Go' --include="*.go" . 2>/dev/null | grep -v 'vendor/' | head -10

Flag:

  • Internal error leaked: http.Error(w, err.Error(), 500) sends raw errors to clients — log internally, return generic message
  • Missing return after http.Error: handler continues, potentially writing multiple responses
  • Goroutine with no error channel/errgroup: errors silently lost
  • Missing recover in long-running goroutines: panic crashes entire program
  • errgroup without context: use errgroup.WithContext to cancel remaining goroutines on first error

6. Error Test Coverage

grep -rn 'wantErr\|expectErr\|shouldErr' --include="*_test.go" . 2>/dev/null | grep -v 'vendor/' | head -15

# Packages with no error path tests
for pkg in $(find . -name "*.go" -not -name "*_test.go" -not -path '*/vendor/*' | sed 's|/[^/]*$||' | sort -u); do
  if [ -z "$(grep -rl 'wantErr\|shouldErr' ${pkg}/*_test.go 2>/dev/null)" ]; then
    echo "NO_ERROR_TESTS: $pkg"
  fi
done | head -10

Output Template

# Go Error Handling Analysis — [Module Name]

## Summary
- Files: N | Go: 1.XX | Error lib: stdlib/pkg/errors
- Critical: N | Warnings: N | Error test coverage: low/moderate/good

## Critical Findings
### [C1] Swallowed Database Error
- **File**: internal/repo/user.go:45
- **Code**: `rows, _ := db.Query(query, args...)`
- **Fix**: Handle error, return `fmt.Errorf("query users: %w", err)`

### [C2] Internal Error Leaked to Client
- **File**: internal/handler/order.go:78
- **Fix**: Log error, return `http.Error(w, "internal server error", 500)`

## Error Wrapping Issues
| File | Line | Issue | Fix |
|------|------|-------|-----|
| repo/user.go | 23 | `%v` not `%w` | Change to `%w` |
| service/order.go | 45 | Bare `return err` | Add context wrapping |

## Sentinel Error Inventory
| Package | Sentinel | Checked With |
|---------|----------|-------------|
| repo | ErrNotFound | errors.Is (correct) |
| auth | ErrExpired | err == (fix: errors.Is) |

## Recommendations
1. Replace N instances of `_ = operation()` with error handling
2. Change N `%v` to `%w` in fmt.Errorf calls
3. Add wrapping context to N bare `return err` statements
4. Install recovery middleware for HTTP server
5. Add error path tests to N packages

Error Handling Checklist

Rule Severity Go Proverb
Never ignore errors Critical "Errors are values"
Wrap with context at boundaries High Each layer adds context
Use %w not %v High Preserve error chains
Use errors.Is not == High Works across wrapping
Use errors.As not type assertion High Works across wrapping
Return after http.Error Critical Prevent double-write
Don't leak internals to clients Critical Security boundary
Propagate errors from goroutines High Silent failures are worst

Tips

  • Run errcheck ./... to catch unchecked errors mechanically
  • Use golangci-lint with wrapcheck, errcheck, goerr113 linters
  • For multi-error scenarios use errors.Join (Go 1.20+) or go.uber.org/multierr
  • Always handle defer Close() errors for writers — flushed data may fail to write