go-error-handling · eduardo-sl — скилл для Claude Code: обработка ошибок в Go
Скилл задаёт агенту правила работы с ошибками в Go: обёртка через %w, sentinel-ошибки, свои типы и errors.Is/errors.As.
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a claude-codeЧто делает go-error-handling
Скилл описывает, как в Go создавать, оборачивать и проверять ошибки. Внутри — дерево решений: простая ошибка через errors.New, добавление контекста через fmt.Errorf с %w, sentinel-ошибки уровня пакета для случаев, когда вызывающий код должен распознать сбой, и свои типы, если ошибка несёт структурированные данные.
Отдельные разделы — про Unwrap у типа-обёртки, про правило обрабатывать ошибку ровно один раз (логировать или возвращать, но не то и другое), про именование (Err для sentinel, суффикс Error для типов, сообщения строчными без точки) и про границы модуля: что публиковать через %w, а что прятать за %v. Есть и правила паники — только для сломанной инициализации и нарушенных инвариантов.
Скилл рассчитан на работу в проекте на Go, поэтому нужен тулчейн Go: без него команды go и gofmt из списка разрешённых инструментов ничего не сделают. Часть материала вынесена в references/patterns-and-boundaries.md и подгружается по необходимости. Если в проекте есть своё правило про обработку ошибок, оно перекрывает этот скилл.
Какие задачи решает
- обернуть ошибку из базы так, чтобы вызывающий код мог её распознать
- завести sentinel-ошибку для «не найдено» и проверять её через
errors.Is - сделать свой тип ошибки с полями и не порвать цепочку
Unwrap - убрать двойную обработку, когда ошибка и логируется, и возвращается
- решить, отдавать ли внутреннюю ошибку наружу или спрятать за
%v
Примеры запросов агенту
- Оберни ошибки в
getUserчерез%wи добавь контекст с id - Заведи sentinel-ошибки для «не найдено» и «нет доступа» и проверяй их через
errors.Is - Сделай тип
ValidationErrorс полемFieldи доставай его черезerrors.As
Как установить go-error-handling
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a claude-code2Вручную: положить файлы
~/.claude/skills/go-error-handling/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/go-error-handling/
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a codex2Вручную: положить файлы
.agents/skills/go-error-handling/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a cursor2Вручную: положить файлы
.cursor/skills/go-error-handling/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install eduardo-sl/go-agent-skills go-error-handling2Вручную: положить файлы
.github/skills/go-error-handling/SKILL.md1Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a gemini-cli2Командой платформы
gemini skills install https://github.com/eduardo-sl/go-agent-skills --path 'skills/(code-quality)/go-error-handling'Вручную: положить файлы
.devin/skills/go-error-handling/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов
Вопросы о go-error-handling
Как установить go-error-handling в Claude Code?
Выполните npx skills add eduardo-sl/go-agent-skills --skill go-error-handling -a claude-code. Скилл окажется в ~/.claude/skills/go-error-handling/SKILL.md.
Что делает go-error-handling?
Даёт агенту набор правил по обработке ошибок в Go: дерево решений для создания ошибок, обёртка с %w, sentinel-ошибки, свои типы с Unwrap, именование и поведение на границах модуля.
Чем этот скилл отличается от go-api-design и go-test-quality?
Он про обработку ошибок в обычном коде. Паника и recover в middleware — это go-api-design, а проверки в тестах — go-test-quality.
Исходный текст
SKILL.md · MIT
---
name: go-error-handling
description: >
Go error handling patterns, wrapping, sentinel errors, custom error types,
and the errors package. Grounded in Effective Go, Go Code Review Comments,
and production-proven idioms. Use when implementing error handling,
designing error types, debugging error chains, or reviewing error handling
patterns. Trigger examples: "handle errors", "error wrapping", "custom
error type", "sentinel errors", "errors.Is", "errors.As".
Not for: panic/recover in middleware (go-api-design), test assertions
(go-test-quality).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*)
metadata:
author: eduardo-sl
version: "1.3.1"
---
# Go Error Handling
Go's explicit error handling is a feature, not a limitation.
These patterns ensure errors are informative, actionable, and properly propagated.
> Community default. A project skill or agent-config rule that explicitly
> supersedes `go-error-handling` takes precedence over this one.
Detailed reference material, loaded on demand:
- `references/patterns-and-boundaries.md` — checking patterns, `Is`
methods, translating errors at the HTTP boundary, low-cardinality logging,
the Go 1.27 vet check on `%w` with pointer errors.
Read a reference file only when the summary below is not enough.
## 1. Error Decision Tree
When creating or returning an error, follow this tree:
1. **Simple, no extra context needed?** → `errors.New("message")`
2. **Need to add context to existing error?** → `fmt.Errorf("doing X: %w", err)`
3. **Caller needs to detect this error?** → Sentinel `var` or custom type
4. **Error carries structured data?** → Custom type implementing `error`
5. **Propagating from downstream?** → Wrap with `%w` and add context
## 2. Sentinel Errors
Use package-level `var` for errors that callers need to check:
```go
// ✅ Good — exported sentinel error
var (
ErrNotFound = errors.New("user: not found")
ErrUnauthorized = errors.New("user: unauthorized")
)
// Naming convention: Err + Description
// Prefix with package context in the message
```
Callers check with `errors.Is`:
```go
if errors.Is(err, user.ErrNotFound) {
// handle not found
}
```
NEVER compare errors with `==`. Always use `errors.Is()`.
## 3. Custom Error Types
When errors need to carry structured information:
```go
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation: field %s: %s", e.Field, e.Message)
}
// Callers extract it from the chain — Go 1.26+:
if valErr, ok := errors.AsType[*ValidationError](err); ok {
log.Printf("invalid field: %s", valErr.Field)
}
// Before Go 1.26:
var valErr *ValidationError
if errors.As(err, &valErr) {
log.Printf("invalid field: %s", valErr.Field)
}
```
A custom type that wraps a cause must expose it, or `errors.Is`/`errors.As`
stop at your type and never see the error underneath:
```go
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string { return "query " + e.Query + ": " + e.Err.Error() }
func (e *QueryError) Unwrap() error { return e.Err } // keeps the chain walkable
```
## 4. Error Wrapping
ALWAYS add context when propagating errors up the stack.
Use `%w` to preserve the error chain:
```go
// ✅ Good — context added, chain preserved
func getUser(id int64) (*User, error) {
row, err := db.QueryRow(ctx, query, id)
if err != nil {
return nil, fmt.Errorf("get user %d: %w", id, err)
}
// ...
}
// ❌ Bad — no context
return nil, err
// ❌ Bad — chain broken, callers can't errors.Is/As
return nil, fmt.Errorf("failed: %v", err)
```
### When NOT to use `%w`
Use `%v` instead of `%w` when you explicitly want to **break** the error chain,
preventing callers from depending on internal implementation errors:
```go
// Intentionally hiding internal DB error from public API
return nil, fmt.Errorf("user lookup failed: %v", err)
```
## 5. Handle Errors Exactly Once
An error should be either logged OR returned, never both:
```go
// ✅ Good — return the error, let caller decide
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
// ...
}
// ❌ Bad — log AND return (error handled twice)
func loadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
log.Printf("failed to read config: %v", err) // handled once
return nil, err // handled again
}
// ...
}
```
The rule: the component that *decides what to do* about the error is the one
that logs/metrics it. Everyone else wraps and returns.
## 6. Error Naming Conventions
```go
// Sentinel errors: Err prefix
var ErrNotFound = errors.New("not found")
// Error types: Error suffix
type NotFoundError struct { ... }
type ValidationError struct { ... }
// Error messages: lowercase, no punctuation, no "failed to" prefix
// Include context: "package: action: detail"
errors.New("auth: token expired")
fmt.Errorf("user: get by id %d: %w", id, err)
```
## 7. Panic Rules
Panic is NOT error handling. Use panic only when:
- Program initialization fails and cannot continue (`template.Must`, flag parsing)
- Programmer error that should never happen (violated invariant)
- Nil dereference that indicates a bug, not a runtime condition
In tests, use `t.Fatal` / `t.FailNow`, never `panic`.
In HTTP handlers and middleware, recover from panics at the boundary
to prevent one request from crashing the server.
## 8. Error Checking Patterns
- Scope the error to the `if` when nothing else needs it:
`if err := do(); err != nil { return fmt.Errorf("do: %w", err) }`.
- A deferred wrap on a named `err` result adds one prefix to every return
path of a long function.
- Collect independent failures (validation, parallel work) and return
`errors.Join(errs...)` — `errors.Is`/`As` see every joined error.
## 9. Errors at Boundaries
- **Publishing an error is a contract.** Inside the module, wrap with `%w`.
At a boundary you publish (an exported library API, an RPC), `%w` makes
the wrapped error part of your API; `%v` or a new error keeps it private.
- **Translate before it reaches a user.** Map domain errors to a status
and a stable message at the handler, and log the original once with its
chain. `err.Error()` in a response leaks SQL, paths and hostnames.
- **Keep logged messages low-cardinality.** The stable text is the message;
IDs, paths and counts go in structured attributes so the log backend can
group identical failures.
- **Panics do not cross goroutines.** A `recover` only works in the
goroutine that panicked — see go-concurrency-review.
- **Implement `Is(target error) bool`** on a type to let `errors.Is` match a
whole family of errors against one sentinel.
Examples for sections 8 and 9: `references/patterns-and-boundaries.md`.
## Executable Verification
After writing or reviewing error handling, run the linters that prove
the rules above (skip any that is not installed and note it):
```bash
go vet ./... # includes printf %w misuse
golangci-lint run --enable errcheck,errorlint # unchecked errors, %v-vs-%w,
# == comparisons on errors
```
## Verification Checklist
1. No `_` discarding errors (unless explicitly justified with comment)
2. Every `fmt.Errorf` wrapping uses `%w` (or `%v` with documented reason)
3. Sentinel errors use `var Err...` naming
4. Custom error types implement `error` interface
5. Callers use `errors.Is` / `errors.As` (`errors.AsType` on Go 1.26+), never `==` or type assertion
6. Custom types that wrap a cause implement `Unwrap() error`
7. No log-and-return patterns
8. Error messages are lowercase, contextual, chain-friendly
9. No `err.Error()` text in responses to users; logged messages are stable, details are attributes
© eduardo-sl · MIT · eduardo-sl/go-agent-skills
Похожие скиллы
Все скиллы по теме «Языки программирования» →Сначала — go-error-handling от других авторов.