npx skills add ...
npx skills add cxuu/golang-skills --skill go-defensive
Use when hardening Go code at API boundaries — copying slices/maps, verifying interface compliance, using defer for cleanup, time.Time/time.Duration, or avoiding mutable globals. Also use when reviewing for robustness concerns like missing cleanup or unsafe crypto usage, even if the user doesn't mention "defensive programming." Does not cover error handling strategy (see go-error-handling).
npx skills add cxuu/golang-skills --skill go-defensive
Compatibility: Crypto examples may use
crypto/rand.Text, which requires Go 1.24+.
references/BOUNDARY-COPYING.md - Read when copying slices/maps across API boundaries.references/GLOBAL-STATE.md - Read when introducing or removing package globals.references/MUST-FUNCTIONS.md - Read when deciding whether a panic-on-error helper is acceptable.references/PANIC-RECOVER.md - Read when evaluating panic, recover, or crash containment.references/TIME-ENUMS-TAGS.md - Read when handling time types, enum zero values, or struct tags.When hardening code at API boundaries, check in this order:
| Pattern | Rule | Details |
|---|---|---|
| Boundary copies | Copy slices/maps on receive and return | BOUNDARY-COPYING.md |
| Defer cleanup | defer f.Close() right after os.Open | Below |
| Interface check | Compile-time satisfaction assertion | See go-interfaces |
| Time types | time.Time / time.Duration, never raw int | TIME-ENUMS-TAGS.md |
| Enum start | iota + 1 so zero = invalid | Below |
| Crypto rand | crypto/rand for keys, never math/rand | Below |
| Must functions | Only at init; panic on failure | MUST-FUNCTIONS.md |
| Panic/recover | Never expose panics across packages | PANIC-RECOVER.md |
| Mutable globals | Replace with dependency injection | Below |
Route compile-time interface assertions to go-interfaces. Use this skill only to notice API-boundary robustness risk; the interface skill owns when an assertion is appropriate and the exact assertion shape.
Slices and maps contain pointers to underlying data. Copy at API boundaries to prevent unintended modifications.
Use defer to clean up resources (files, locks). Avoids missed cleanup on multiple return paths.
Defer overhead is negligible. Place defer f.Close() immediately after
os.Open for clarity. Arguments to deferred functions are evaluated when
defer executes, not when the function runs. Multiple defers execute in
LIFO order.
Advisory: Always add explicit field tags to structs that are marshaled or unmarshaled.
Field tags are a serialization contract — renaming a struct field without updating the tag silently breaks wire compatibility. Treat tags as part of the public API for any type that crosses a serialization boundary.
Start enums at non-zero to distinguish uninitialized from valid values.
Exception: When zero is the sensible default (e.g., LogToStdout = iota).
Inject dependencies instead of mutating package-level variables. This makes code testable without global save/restore.
Do not use math/rand or math/rand/v2 to generate keys — this is a
security concern. Time-seeded generators have predictable output.
For text output, use crypto/rand.Text directly, or encode random bytes
with encoding/hex or encoding/base64.
Use panic only for truly unrecoverable situations. Library functions
should avoid panic.
Key rules:
init() if a library truly cannot set itself upMust functions panic on error — use them only during program
initialization where failure means the program cannot run.
// Receiving: copy incoming slice
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
// Returning: copy map before returning
result := make(map[string]int, len(s.counters))
for k, v := range s.counters { result[k] = v }p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.counttype User struct {
Name string `json:"name" yaml:"name"`
Email string `json:"email" yaml:"email"`
}const (
Add Operation = iota + 1 // Add=1, zero value = uninitialized
Subtract
Multiply
)type signer struct {
now func() time.Time // injected; tests replace with fixed time
}
func newSigner() *signer {
return &signer{now: time.Now}
}import "crypto/rand"
func Key() string { return rand.Text() }func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}var validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)
var tmpl = template.Must(template.ParseFiles("index.html"))