Defensive Golang coding to prevent panics, silent data corruption, and subtle runtime bugs. Use when encountering nil panics, append aliasing, map concurrent access, float comparison pitfalls, or zero-value design questions. Also use when reviewing code for nil-safety, numeric conversion overflow, resource lifecycle issues (defer in loops), or defensive copying of slices and maps.
npx skills add https://github.com/samber/cc-skills-golang --skill golang-safety
Persona: You are a defensive Go engineer. You treat every untested assumption about nil, capacity, and numeric range as a latent crash waiting to happen.
Prevents programmer mistakes — bugs, panics, and silent data corruption in normal (non-adversarial) code. Security handles attackers; safety handles ourselves.
any when the type set is known — compiler catches mismatches instead of runtime panicsv, ok := x.(T)); for reflection in Go 1.25+ prefer reflect.TypeAssertT over value.Interface().(T).== nil — the type descriptor makes it non-nilappend may reuse the backing array — both slices share memory if capacity allows, silently corrupting each otherdefer runs at function exit, not loop iteration — extract loop body to a functionint64 to int32 wraps without errormath/big10. Design useful zero values — nil map fields panic on first write; use lazy init
11. Use sync.Once for lazy init — guarantees exactly-once even under concurrency
Nil-related panics are the most common crash in Go.
Interfaces store (type, value). An interface is nil only when both are nil. Returning a typed nil pointer sets the type descriptor, making it non-nil:
// ✗ Dangerous — interface{type: *MyHandler, value: nil} is not == nil
func getHandler() http.Handler {
var h *MyHandler // nil pointer
if !enabled {
return h // interface{type: *MyHandler, value: nil} != nil
}
return h
}
// ✓ Good — return nil explicitly
func getHandler() http.Handler {
if !enabled {
return nil // interface{type: nil, value: nil} == nil
}
return &MyHandler{}
}
| Type | Index into nil | Write to nil | Len/Cap of nil | Range over nil |
| ------- | -------------- | -------------- | -------------- | -------------- |
| Map | Zero value | panic | 0 | 0 iterations |
| Slice | panic | panic | 0 | 0 iterations |
| Channel | Blocks forever | Blocks forever | 0 | Blocks forever |
// ✗ Bad — nil map panics on write
var m map[string]int
m["key"] = 1
// ✓ Good — initialize or lazy-init in methods
m := make(map[string]int)
func (r *Registry) Add(name string, val int) {
if r.items == nil { r.items = make(map[string]int) }
r.items[name] = val
}
See Nil Safety Deep Dive for nil receivers, nil in generics, and nil interface performance.
append reuses the backing array if capacity allows. Both slices then share memory:
// ✗ Dangerous — a and b share backing array
a := make([]int, 3, 5)
b := append(a, 4)
b[0] = 99 // also modifies a[0]
// ✓ Good — full slice expression forces new allocation
b := append(a[:len(a):len(a)], 4)
Maps MUST NOT be accessed concurrently — → see samber/cc-skills-golang@golang-concurrency for sync primitives.
See Slice and Map Deep Dive for range pitfalls, subslice memory retention, and slices.Clone/maps.Clone.
// ✗ Bad — silently wraps around if val > math.MaxInt32 (3B becomes -1.29B)
var val int64 = 3_000_000_000
i32 := int32(val) // -1294967296 (silent wraparound)
// ✓ Good — check before converting
if val > math.MaxInt32 || val < math.MinInt32 {
return fmt.Errorf("value %d overflows int32", val)
}
i32 := int32(val)
// ✗ Bad — floating point arithmetic is not exact
var a, b, c float64 = 0.1, 0.2, 0.3
a+b == c // false
// ✓ Good — use epsilon comparison
const epsilon = 1e-9
math.Abs((a+b)-c) < epsilon // true
Integer division by zero panics. Float division by zero produces +Inf, -Inf, or NaN.
func avg(total, count int) (int, error) {
if count == 0 {
return 0, errors.New("division by zero")
}
return total / count, nil
}
For integer overflow as a security vulnerability, see the samber/cc-skills-golang@golang-security skill section.
defer runs at _function_ exit, not loop iteration. Resources accumulate until the function returns:
// ✗ Bad — all files stay open until function returns
for _, path := range paths {
f, _ := os.Open(path)
defer f.Close() // deferred until function exits
process(f)
}
// ✓ Good — extract to function so defer runs per iteration
for _, path := range paths {
if err := processOne(path); err != nil { return err }
}
func processOne(path string) error {
f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
return process(f)
}
→ See samber/cc-skills-golang@golang-concurrency for goroutine lifecycle and leak prevention.
Exported functions returning slices/maps SHOULD return defensive copies.
// ✗ Bad — exported slice field, anyone can mutate
type Config struct {
Hosts []string
}
// ✓ Good — unexported field with accessor returning a copy
type Config struct {
hosts []string
}
func (c *Config) Hosts() []string {
return slices.Clone(c.hosts)
}
Design types so var x MyType is safe — prevents "forgot to initialize" bugs:
var mu sync.Mutex // ✓ usable at zero value
var buf bytes.Buffer // ✓ usable at zero value
// ✗ Bad — nil map panics on write
type Cache struct { data map[string]any }
type DB struct {
once sync.Once
conn *sql.DB
}
func (db *DB) connection() *sql.DB {
db.once.Do(func() {
db.conn, _ = sql.Open("postgres", connStr)
})
return db.conn
}
→ See samber/cc-skills-golang@golang-design-patterns for why init() should be avoided in favor of explicit constructors.
Many safety pitfalls are caught automatically by linters: errcheck, forcetypeassert, nilerr, govet, staticcheck. See the samber/cc-skills-golang@golang-lint skill for configuration and usage.
For reflection code, prefer reflect.TypeAssert[T] over value.Interface().(T).
v := reflect.ValueOf(x)
if s, ok := reflect.TypeAssert[string](v); ok {
use(s)
}
samber/cc-skills-golang@golang-concurrency skill for concurrent access patterns and sync primitivessamber/cc-skills-golang@golang-data-structures skill for slice/map internals, capacity growth, and container/ packagessamber/cc-skills-golang@golang-error-handling skill for nil error interface trapsamber/cc-skills-golang@golang-security skill for security-relevant safety issues (memory safety, integer overflow)samber/cc-skills-golang@golang-troubleshooting skill for debugging panics and race conditions| Mistake | Fix |
| --- | --- |
| Bare type assertion v := x.(T) | Panics on type mismatch, crashing the program. Use v, ok := x.(T) to handle gracefully |
| Returning typed nil in interface function | Interface holds (type, nil) which is != nil. Return untyped nil for the nil case |
| Writing to a nil map | Nil maps have no backing storage — write panics. Initialize with make(map[K]V) or lazy-init |
| Assuming append always copies | If capacity allows, both slices share the backing array. Use s[:len(s):len(s)] to force a copy |
| defer in a loop | defer runs at function exit, not loop iteration — resources accumulate. Extract body to a separate function |
| int64 to int32 without bounds check | Values wrap silently (3B → -1.29B). Check against math.MaxInt32/math.MinInt32 first |
| Comparing floats with == | IEEE 754 representation is not exact (0.1+0.2 != 0.3). Use math.Abs(a-b) < epsilon |
| Integer division without zero check | Integer division by zero panics. Guard with if divisor == 0 before dividing |
| Returning internal slice/map reference | Callers can mutate your struct's internals through the shared backing array. Return a defensive copy |
| Multiple init() with ordering assumptions | init() execution order across files is unspecified. → See samber/cc-skills-golang@golang-design-patterns — use explicit constructors |
| Blocking forever on nil channel | Nil channels block on both send and receive. Always initialize before use |
samber/cc-skills-golang@golang-continuous-integration skill for automated AI-driven code review in CI using these guidelinesGuide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
Automatically creates user-facing changelogs from git commits by analyzing commit history, categorizing changes, and transforming technical commits into clear, customer-friendly release notes. Turns hours of manual changelog writing into minutes of automated generation.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling
Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
Take samber/golang-safety from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.