{"id":"39e586b4-9703-4e4f-8bf2-246aa65e7add","shortId":"zV9U69","kind":"skill","title":"go-error-handling","tagline":"Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is","description":"# Go Error Handling\n\n## Available Scripts\n\n- **`scripts/check-errors.sh`** — Detects error handling anti-patterns: string comparison on `err.Error()`, bare `return err` without context, and log-and-return violations. Run `bash scripts/check-errors.sh --help` for options.\n\nIn Go, [errors are values](https://go.dev/blog/errors-are-values) — they are\ncreated by code and consumed by code.\n\n## Choosing an Error Strategy\n\n1. System boundary (RPC, IPC, storage)? → Wrap with `%v` to avoid leaking internals\n2. Caller needs to match specific conditions? → Sentinel or typed error, wrap with `%w`\n3. Caller just needs debugging context? → `fmt.Errorf(\"...: %w\", err)`\n4. Leaf function, no wrapping needed? → Return the error directly\n\n**Default**: wrap with `%w` and place it at the end of the format string.\n\n---\n\n## Core Rules\n\n### Never Return Concrete Error Types\n\n**Never return concrete error types from exported functions** — a concrete `nil`\npointer can become a non-nil interface:\n\n```go\n// Bad: Concrete type can cause subtle bugs\nfunc Bad() *os.PathError { /*...*/ }\n\n// Good: Always return the error interface\nfunc Good() error { /*...*/ }\n```\n\n### Error Strings\n\nError strings should **not** be capitalized and should **not** end with\npunctuation. Exception: exported names, proper nouns, or acronyms.\n\n```go\n// Bad\nerr := fmt.Errorf(\"Something bad happened.\")\n\n// Good\nerr := fmt.Errorf(\"something bad happened\")\n```\n\nFor displayed messages (logs, test failures, API responses), capitalization is\nappropriate.\n\n### Return Values on Error\n\nWhen a function returns an error, callers must treat all non-error return values\nas unspecified unless explicitly documented.\n\n**Tip**: Functions taking a `context.Context` should usually return an `error`\nso callers can determine if the context was cancelled.\n\n---\n\n## Handling Errors\n\nWhen encountering an error, make a **deliberate choice** — do not discard\nwith `_`:\n\n1. **Handle immediately** — address the error and continue\n2. **Return to caller** — optionally wrapped with context\n3. **In exceptional cases** — `log.Fatal` or `panic`\n\nTo intentionally ignore: add a comment explaining why.\n\n```go\nn, _ := b.Write(p) // never returns a non-nil error\n```\n\nFor related concurrent operations, use\n[`errgroup`](https://pkg.go.dev/golang.org/x/sync/errgroup):\n\n```go\ng, ctx := errgroup.WithContext(ctx)\ng.Go(func() error { return task1(ctx) })\ng.Go(func() error { return task2(ctx) })\nif err := g.Wait(); err != nil { return err }\n```\n\n### Avoid In-Band Errors\n\nDon't return `-1`, `nil`, or empty string to signal errors. Use multiple\nreturns:\n\n```go\n// Bad: In-band error value\nfunc Lookup(key string) int  // returns -1 for missing\n\n// Good: Explicit error or ok value\nfunc Lookup(key string) (string, bool)\n```\n\nThis prevents callers from writing `Parse(Lookup(key))` — it causes a\ncompile-time error since `Lookup(key)` has 2 outputs.\n\n---\n\n## Error Flow\n\nHandle errors before normal code. Early returns keep the happy path unindented:\n\n```go\n// Good: Error first, normal code unindented\nif err != nil {\n    return err\n}\n// normal code\n```\n\n**Handle errors once** — either log or return, never both:\n\n```\nError encountered?\n├─ Caller can act on it? → Return (with context via %w)\n├─ Top of call chain? → Log and handle\n└─ Neither? → Log at appropriate level, continue\n```\n\n> Read [references/ERROR-FLOW.md](references/ERROR-FLOW.md) when structuring complex error flows, deciding between logging vs returning, implementing the handle-once pattern, or choosing structured logging levels.\n\n---\n\n## Error Types\n\n> **Advisory**: Recommended best practice.\n\n| Caller needs to match? | Message type | Use |\n|------------------------|--------------|-----|\n| No | static | `errors.New(\"message\")` |\n| No | dynamic | `fmt.Errorf(\"msg: %v\", val)` |\n| Yes | static | `var ErrFoo = errors.New(\"...\")` |\n| Yes | dynamic | custom `error` type |\n\n**Default**: Wrap with `fmt.Errorf(\"...: %w\", err)`. Escalate to sentinels for\n`errors.Is()`, to custom types for `errors.As()`.\n\n> Read [references/ERROR-TYPES.md](references/ERROR-TYPES.md) when defining sentinel errors, creating custom error types, or choosing error strategies for a package API.\n\n---\n\n## Error Wrapping\n\n> **Advisory**: Recommended best practice.\n\n- **Use `%v`**: At system boundaries, for logging, to hide internal details\n- **Use `%w`**: To preserve error chain for `errors.Is`/`errors.As`\n\n**Key rules**: Place `%w` at the end. Add context callers don't have. If\nannotation adds nothing, return `err` directly.\n\n> Read [references/WRAPPING.md](references/WRAPPING.md) when deciding between %v and %w, wrapping errors across package boundaries, or adding contextual information.\n\n> **Validation**: After implementing error handling, run `bash scripts/check-errors.sh` to detect common anti-patterns. Then run `go vet ./...` to catch additional issues.\n\n---\n\n## Related Skills\n\n- **Error naming**: See [go-naming](../go-naming/SKILL.md) when naming sentinel errors (`ErrFoo`) or custom error types\n- **Testing errors**: See [go-testing](../go-testing/SKILL.md) when testing error semantics with `errors.Is`/`errors.As` or writing error-checking helpers\n- **Panic handling**: See [go-defensive](../go-defensive/SKILL.md) when deciding between panic and error returns, or writing recover guards\n- **Guard clauses**: See [go-control-flow](../go-control-flow/SKILL.md) when structuring early-return error flow or reducing nesting\n- **Logging decisions**: See [go-logging](../go-logging/SKILL.md) when choosing log levels, configuring structured logging, or deciding what context to include in log messages","tags":["error","handling","golang","skills","cxuu","agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor"],"capabilities":["skill","source-cxuu","skill-go-error-handling","topic-agent-skills","topic-ai-agent","topic-ai-assistant","topic-claude","topic-claude-code","topic-codex","topic-cursor","topic-golang","topic-llm"],"categories":["golang-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/cxuu/golang-skills/go-error-handling","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add cxuu/golang-skills","source_repo":"https://github.com/cxuu/golang-skills","install_from":"skills.sh"}},"qualityScore":"0.491","qualityRationale":"deterministic score 0.49 from registry signals: · indexed on github topic:agent-skills · 82 github stars · SKILL.md body (5,609 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-02T12:55:18.172Z","embedding":null,"createdAt":"2026-04-18T22:13:13.481Z","updatedAt":"2026-05-02T12:55:18.172Z","lastSeenAt":"2026-05-02T12:55:18.172Z","tsv":"'-1':393,417 '/blog/errors-are-values)':88 '/go-control-flow/skill.md':756 '/go-defensive/skill.md':737 '/go-logging/skill.md':773 '/go-naming/skill.md':701 '/go-testing/skill.md':717 '/golang.org/x/sync/errgroup):':360 '1':102,310 '2':115,318,451 '3':129,326 '4':138 'acronym':228 'across':42,664 'act':494 'ad':668 'add':336,640,648 'addit':691 'address':313 'advisori':541,609 'also':37 'alway':200 'annot':647 'anti':58,683 'anti-pattern':57,682 'api':248,606 'appropri':252,512 'avail':51 'avoid':112,385 'b.write':343 'bad':189,197,230,234,240,405 'band':388,408 'bare':64 'bash':76,677 'becom':182 'best':543,611 'bool':431 'boundari':44,104,617,666 'bug':195 'call':504 'caller':116,130,263,288,321,434,492,545,642 'cancel':295 'capit':215,250 'case':329 'catch':690 'caus':193,441 'chain':505,629 'check':729 'choic':305 'choos':16,98,535,600,775 'claus':750 'code':9,93,97,459,472,480 'comment':338 'common':681 'comparison':61 'compil':444 'compile-tim':443 'complex':520 'concret':166,171,178,190 'concurr':354 'condit':121 'configur':778 'consum':95 'context':68,134,293,325,499,641,784 'context.context':281 'contextu':669 'continu':317,514 'control':754 'core':162 'creat':91,595 'ctx':363,365,371,377 'custom':20,569,584,596,708 'debug':133 'decid':31,523,657,739,782 'decis':768 'default':148,572 'defens':736 'defin':592 'deliber':304 'detail':623 'detect':54,680 'determin':290 'direct':147,652 'discard':308 'display':243 'document':276 'dynam':557,568 'earli':460,760 'early-return':759 'either':484 'empti':396 'encount':299,491 'end':157,219,639 'err':66,137,231,237,379,381,384,475,478,577,651 'err.error':63 'errfoo':565,706 'errgroup':357 'errgroup.withcontext':364 'error':3,15,19,28,41,49,55,83,100,125,146,167,172,203,207,208,210,256,262,269,286,297,301,315,351,368,374,389,400,409,422,446,453,456,469,482,490,521,539,570,594,597,601,607,628,663,674,695,705,709,712,720,728,743,762 'error-check':727 'errors.as':587,632,724 'errors.is':47,582,631,723 'errors.new':554,566 'escal':578 'except':222,328 'explain':339 'explicit':275,421 'export':175,223 'failur':247 'first':470 'flow':29,454,522,755,763 'fmt.errorf':23,135,232,238,558,575 'format':160 'func':196,205,367,373,411,426 'function':140,176,259,278 'g':362 'g.go':366,372 'g.wait':380 'go':2,8,48,82,188,229,341,361,404,467,687,699,715,735,753,771 'go-control-flow':752 'go-defens':734 'go-error-handl':1 'go-log':770 'go-nam':698 'go-test':714 'go.dev':87 'go.dev/blog/errors-are-values)':86 'good':199,206,236,420,468 'guard':748,749 'handl':4,14,50,56,296,311,455,481,508,531,675,732 'handle-onc':530 'happen':235,241 'happi':464 'help':78 'helper':730 'hide':621 'ignor':335 'immedi':312 'implement':528,673 'in-band':386,406 'includ':786 'inform':670 'int':415 'intent':334 'interfac':187,204 'intern':114,622 'ipc':106 'issu':692 'keep':462 'key':413,428,439,449,633 'leaf':139 'leak':113 'level':513,538,777 'log':34,71,245,485,506,510,525,537,619,767,772,776,780,788 'log-and-return':70 'log.fatal':330 'lookup':412,427,438,448 'make':302 'match':119,548 'messag':244,549,555,789 'miss':419 'msg':559 'multipl':402 'must':264 'n':342 'name':224,696,700,703 'need':117,132,143,546 'neither':509 'nest':766 'never':164,169,345,488 'nil':179,186,350,382,394,476 'non':185,268,349 'non-error':267 'non-nil':184,348 'normal':458,471,479 'noth':649 'noun':226 'ok':424 'oper':355 'option':80,322 'os.patherror':198 'output':452 'p':344 'packag':43,605,665 'panic':332,731,741 'pars':437 'path':465 'pattern':59,533,684 'pkg.go.dev':359 'pkg.go.dev/golang.org/x/sync/errgroup):':358 'place':153,635 'pointer':180 'practic':544,612 'preserv':627 'prevent':433 'propag':40 'proper':225 'punctuat':221 'read':515,588,653 'recommend':542,610 'recov':747 'reduc':765 'references/error-flow.md':516,517 'references/error-types.md':589,590 'references/wrapping.md':654,655 'relat':353,693 'respons':249 'return':11,36,65,73,144,165,170,201,253,260,270,284,319,346,369,375,383,392,403,416,461,477,487,497,527,650,744,761 'rpc':105 'rule':163,634 'run':75,676,686 'script':52 'scripts/check-errors.sh':53,77,678 'see':697,713,733,751,769 'semant':721 'sentinel':18,122,580,593,704 'signal':399 'sinc':447 'skill':694 'skill-go-error-handling' 'someth':233,239 'source-cxuu' 'specif':120 'static':553,563 'storag':107 'strategi':101,602 'string':60,161,209,211,397,414,429,430 'structur':27,519,536,758,779 'subtl':194 'system':103,616 'take':279 'task1':370 'task2':376 'test':246,711,716,719 'time':445 'tip':277 'top':502 'topic-agent-skills' 'topic-ai-agent' 'topic-ai-assistant' 'topic-claude' 'topic-claude-code' 'topic-codex' 'topic-cursor' 'topic-golang' 'topic-llm' 'treat':265 'type':21,124,168,173,191,540,550,571,585,598,710 'unind':466,473 'unless':274 'unspecifi':273 'use':5,38,46,356,401,551,613,624 'usual':283 'v':26,110,560,614,659 'val':561 'valid':671 'valu':85,254,271,410,425 'var':564 'vet':688 'via':500 'violat':74 'vs':25,526 'w':24,128,136,151,501,576,625,636,661 'whether':32 'without':67 'wrap':12,108,126,142,149,323,573,608,662 'write':7,436,726,746 'yes':562,567","prices":[{"id":"2dce256d-529b-4715-9b4e-013c51310331","listingId":"39e586b4-9703-4e4f-8bf2-246aa65e7add","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"cxuu","category":"golang-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:13:13.481Z"}],"sources":[{"listingId":"39e586b4-9703-4e4f-8bf2-246aa65e7add","source":"github","sourceId":"cxuu/golang-skills/go-error-handling","sourceUrl":"https://github.com/cxuu/golang-skills/tree/main/skills/go-error-handling","isPrimary":false,"firstSeenAt":"2026-04-18T22:13:13.481Z","lastSeenAt":"2026-05-02T12:55:18.172Z"}],"details":{"listingId":"39e586b4-9703-4e4f-8bf2-246aa65e7add","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"cxuu","slug":"go-error-handling","github":{"repo":"cxuu/golang-skills","stars":82,"topics":["agent-skills","ai-agent","ai-assistant","claude","claude-code","codex","cursor","go","golang","llm"],"license":"apache-2.0","html_url":"https://github.com/cxuu/golang-skills","pushed_at":"2026-03-15T19:32:10Z","description":"AI Agent Skills for idiomatic, production-ready Go code, distilled from Google, Uber, Community","skill_md_sha":"83aeec5aa6226a29f2bd6ff9eef3a5b55fbdc71b","skill_md_path":"skills/go-error-handling/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/cxuu/golang-skills/tree/main/skills/go-error-handling"},"layout":"multi","source":"github","category":"golang-skills","frontmatter":{"name":"go-error-handling","license":"Apache-2.0","description":"Use when writing Go code that returns, wraps, or handles errors — choosing between sentinel errors, custom types, and fmt.Errorf (%w vs %v), structuring error flow, or deciding whether to log or return. Also use when propagating errors across package boundaries or using errors.Is/As, even if the user doesn't ask about error strategy. Does not cover panic/recover patterns (see go-defensive).","compatibility":"Requires Go 1.13+ for errors.Is/errors.As and fmt.Errorf %w wrapping. Structured logging examples use slog (Go 1.21+)."},"skills_sh_url":"https://skills.sh/cxuu/golang-skills/go-error-handling"},"updatedAt":"2026-05-02T12:55:18.172Z"}}