← GophersCONTENT HISTORY

Update to Gophers

Snapshot Sep 30, 2026 · 23:14 UTC · version 0.1.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Use when hardening Go code at API boundaries: copy slices/maps on entry and return, defer cleanup, verify interface compliance at compile time, model time with time.Time/time.Duration, design enum zero values, prefer crypto/rand, and inject clocks for testability. Apply proactively when reviewing for robustness. Error-handling strategy: see go-error-handling.",
  "included_files": [
    {
      "relative_path": "agents/openai.yaml",
      "size_in_bytes": 226
    },
    {
      "relative_path": "references/boundary-copying.md",
      "size_in_bytes": 2290
    },
    {
      "relative_path": "references/must-and-panic.md",
      "size_in_bytes": 2541
    },
    {
      "relative_path": "references/time-and-enums.md",
      "size_in_bytes": 2328
    }
  ],
  "name": "go-defensive",
  "skill_md_contents": "---\nname: go-defensive\ndescription: \"Use when hardening Go code at API boundaries: copy slices/maps on entry and return, defer cleanup, verify interface compliance at compile time, model time with time.Time/time.Duration, design enum zero values, prefer crypto/rand, and inject clocks for testability. Apply proactively when reviewing for robustness. Error-handling strategy: see go-error-handling.\"\nlicense: MIT\ncompatibility: \"Designed for Claude Code or similar AI coding agents. `crypto/rand.Text` examples assume Go 1.24+.\"\nallowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(golangci-lint:*)\n---\n\n# Go Defensive Programming\n\nHardening Go code is not paranoia — it is the discipline of making your boundaries honest. Copy what crosses them, clean up what you opened, model time and randomness honestly, and never let a panic escape a package.\n\n## Core Rules\n\n1. **Copy slices and maps at API boundaries.** They are reference types — leaking the backing array leaks mutation.\n2. **`defer` the cleanup right after the acquire.** `f, err := os.Open(...); defer f.Close()`.\n3. **Verify interface compliance at compile time:** `var _ I = (*T)(nil)`.\n4. **Model time and durations with `time.Time` and `time.Duration`,** never raw ints.\n5. **Inject `now func() time.Time`** instead of calling `time.Now()` directly in production code.\n6. **Enums start at `iota + 1`** so the zero value is invalid.\n7. **`crypto/rand` for secrets, never `math/rand`.**\n8. **Panics never cross package boundaries.** Convert to errors at the edge.\n9. **Avoid mutable package-level state.** Inject dependencies instead.\n\n## Boundary Hardening Checklist\n\nWhen you touch an exported function or method, walk this list in order:\n\n| # | Check |\n|---|---|\n| 1 | Return errors, don't panic across boundaries |\n| 2 | Copy slices/maps you'll retain |\n| 3 | Copy slices/maps you'll return if internal state aliases them |\n| 4 | `defer` Close / Unlock / cancel right after the acquire |\n| 5 | Compile-time interface satisfaction check |\n| 6 | `time.Time` / `time.Duration` types, injected clock |\n| 7 | Enum zero = invalid (`iota + 1`) |\n| 8 | `crypto/rand` for any secret material |\n\n## Copy at API Boundaries\n\n```go\n// Receiving: copy a slice we'll retain\nfunc (d *Driver) SetTrips(trips []Trip) {\n    d.trips = make([]Trip, len(trips))\n    copy(d.trips, trips)\n}\n\n// Returning: copy a map so callers can't mutate our state\nfunc (s *Stats) Snapshot() map[string]int {\n    out := make(map[string]int, len(s.counters))\n    for k, v := range s.counters {\n        out[k] = v\n    }\n    return out\n}\n```\n\n> Read [references/boundary-copying.md](references/boundary-copying.md) when deciding which boundaries actually need copies (and when copying is wasted work).\n\n## Defer Cleanup\n\n`defer` evaluates arguments at the `defer` statement and runs the call when the surrounding function returns (LIFO order):\n\n```go\nf, err := os.Open(name)\nif err != nil {\n    return err\n}\ndefer f.Close()\n```\n\nPlace `defer` immediately after the acquire — the proximity makes pair-correctness reviewable at a glance.\n\nFor locks:\n\n```go\nmu.Lock()\ndefer mu.Unlock()\n```\n\nBeware of `defer` inside loops — accumulated defers run only when the function returns, not when the iteration ends.\n\n## Verify Interface Compliance\n\n```go\nvar _ http.Handler = (*Handler)(nil)\n```\n\nIf `(*Handler)` ever stops satisfying `http.Handler`, the build fails. The line costs nothing at runtime and gives you a free contract.\n\n## Time Modeling\n\n```go\n// Bad — what unit is timeout?\ntype Config struct {\n    Timeout int\n}\n\n// Good\ntype Config struct {\n    Timeout time.Duration\n}\n```\n\nFor wall-clock work, inject the clock so tests can pin time:\n\n```go\ntype Signer struct {\n    now func() time.Time\n}\n\nfunc NewSigner() *Signer {\n    return &Signer{now: time.Now}\n}\n\n// In tests:\ns := &Signer{now: func() time.Time { return fixedTime }}\n```\n\n> Read [references/time-and-enums.md](references/time-and-enums.md) for monotonic time, time zones, struct tags, and embedding tradeoffs.\n\n## Crypto Random\n\n```go\nimport \"crypto/rand\"\n\n// Go 1.24+\nfunc APIKey() string { return rand.Text() }\n```\n\n`math/rand` and `math/rand/v2` are predictable from a seed — never use them for keys, tokens, nonces, or any secret material.\n\n## Must Functions\n\n`Must*` helpers panic on error. They are appropriate **only** at program initialization, where failure means the program cannot start:\n\n```go\nvar (\n    validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)\n    tmpl    = template.Must(template.ParseFiles(\"index.html\"))\n)\n```\n\nDon't write `MustFoo` for runtime call sites — it shifts an error condition into a crash.\n\n> Read [references/must-and-panic.md](references/must-and-panic.md) for writing custom `Must*`, recovering at goroutine boundaries, and distinguishing `panic` from `log.Fatal`.\n\n## Avoid Mutable Globals\n\n```go\n// Bad — testing requires save/restore dance\nvar DB *sql.DB\n\n// Good — pass the dependency\ntype Service struct {\n    db *sql.DB\n}\n```\n\nConstants and once-initialized lookup tables are fine. Mutable package-level vars are a code smell.\n\n## Anti-Patterns\n\n| Anti-pattern | Why it hurts | Do this instead |\n|---|---|---|\n| Storing the caller's slice without copying | Mutation aliasing | `make` + `copy` |\n| Returning the internal map directly | External mutation of state | Return a snapshot |\n| `time.Now()` in business logic | Hostile to tests | Inject `now func() time.Time` |\n| `var Timeout = 5` read as seconds elsewhere | Ambiguous unit | `time.Duration` |\n| `math/rand` for keys | Predictable from seed | `crypto/rand` |\n| `panic` to signal a domain error | Crashes the caller | Return an error |\n| `defer` inside a tight loop | Defers stack until function return | Wrap loop body in a function |\n\n## Verification Checklist\n\n- [ ] Slices/maps stored from callers, or returned aliasing internal state, are copied\n- [ ] Every `Open`/`Lock` has a `defer Close`/`Unlock` next to it\n- [ ] Compile-time interface checks cover exported implementations\n- [ ] Durations are `time.Duration`, timestamps are `time.Time`; clock is injected\n- [ ] Enum zero values are invalid (or explicitly meaningful)\n- [ ] No secret material derived from `math/rand`\n- [ ] No mutable package-level vars; no `panic` across library boundaries\n\n## References\n\n- [references/boundary-copying.md](references/boundary-copying.md) — when defensive copies pay off vs. wasted allocation\n- [references/time-and-enums.md](references/time-and-enums.md) — modeling time, durations, enums, struct tags\n- [references/must-and-panic.md](references/must-and-panic.md) — `Must*` helpers, recover at boundaries, panic vs `log.Fatal`\n"
}

SHA-256 of public snapshot: 88c50697c9be8d77d8b36b3c609e30ccc31c6cea399bae5ccd3f0de916ecbd26