К основному содержанию
скилл CX Codex

go-defensive-coding — скилл для Claude Code: обзор и установка

Защищает код на Go от паник и скрытых багов: типизированные nil в интерфейсах, алиасинг слайсов, переполнение чисел и утечки ресурсов.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add eduardo-sl/go-agent-skills --skill go-defensive-coding -a claude-code
Открыть на GitHub

Что делает go-defensive-coding

Скилл внедряет в диалог с агентом набор правил защитного программирования на Go. Он помогает избегать неочевидных ловушек компилятора: интерфейсов с типизированным nil, перезаписи чужой памяти через append по срезу и тихого усечения значений при приведении чисел вроде int64 к int32.

Работает в двух режимах. В режиме Harden агент сразу пишет безопасный код с правильным копированием структур через slices.Clone и проверками диапазонов. В режиме Review он размечает найденные проблемы по уровням опасности: от паник и порчи данных до стилевых правок с указанием файлов и строк.

Для запуска проверок агенту требуется установленный в системе Go toolchain. Утилиты golangci-lint и gosec остаются опциональными, но без них часть скрытых проблем не подтвердить линтером.

Какие задачи решает

  • Предотвратить паники из-за скрытых типизированных nil в интерфейсах
  • Исключить перезапись данных при работе со срезами слайсов
  • Проверять диапазоны значений перед числовыми преобразованиями
  • Найти отложенные вызовы defer внутри циклов

Примеры запросов агенту

  • Проверь этот пакет на nil-safety и утечки памяти
  • Сделай безопасным метод экспорта конфига, чтобы вызывающий код не мутировал срез
  • Найди места, где сужение числовых типов может привести к потере данных

Как установить go-defensive-coding

1Через Skills CLI (Vercel)

npx skills add eduardo-sl/go-agent-skills --skill go-defensive-coding -a claude-code

2Вручную: положить файлы

~/.claude/skills/go-defensive-coding/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/go-defensive-coding/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о go-defensive-coding

Как установить go-defensive-coding в Claude Code?

Выполните в терминале команду npx skills add eduardo-sl/go-agent-skills --skill go-defensive-coding -a claude-code. Файл навыка сохранится по пути ~/.claude/skills/go-defensive-coding/SKILL.md.

Что делает этот скилл?

Он направляет агента при ревью и написании Go-кода, отслеживая мутации слайсов, вызовы defer в циклах, сравнение time.Time через == и ловушки интерфейсов.

Подходит ли скилл для аудита конкурентности или гонок данных?

Нет, автор скилла прямо исключает разбор жизненного цикла горутин и гонок данных — для таких задач в репозитории предусмотрен отдельный навык go-concurrency-review.

Исходный текст

SKILL.md · MIT
---
name: go-defensive-coding
description: >
  Prevent panics, silent corruption, and subtle runtime bugs in Go:
  typed-nil interfaces, slice aliasing, integer overflow on conversion,
  float comparison, defer in loops, defensive copying at API boundaries, and
  zero-value design. Use when hardening code against crashes, reviewing for
  nil-safety, converting between numeric types, or deciding what to copy at
  a package boundary. Trigger examples: "nil pointer panic", "why is my
  error not nil", "slice aliasing", "integer overflow", "compare floats",
  "defer in a loop", "defensive copy", "make this crash-proof".
  Not for: data races and goroutine lifecycle (go-concurrency-review),
  injection and auth (go-security-audit), a panic that already happened
  (go-troubleshooting).
user-invocable: true
license: MIT
compatibility: Designed for Claude Code or similar AI coding agents working on Go projects. Requires the Go toolchain. golangci-lint and gosec are optional, for enforcing the rules below.
allowed-tools: Read Edit Write Glob Grep Bash(go:*) Bash(gofmt:*) Bash(golangci-lint:*) Bash(gosec:*)
metadata:
  author: eduardo-sl
  version: "1.1.3"
---

# Go Defensive Coding

Go has no exceptions and no null-safety in the type system. Every trap below
compiles cleanly, passes review, and fails in production.

Detailed reference material, loaded on demand:

- `references/nil-and-aliasing.md` — the full typed-nil rules, slice
  aliasing scenarios, and memory retention.
- `references/numeric-safety.md` — conversion range checks, overflow
  detection, float and time comparison.
- `references/copies-and-lifecycle.md` — `copylocks`/`noCopy`, `defer` in
  loops, checking deferred `Close` errors, usable zero values.

Read a reference file only when the section below is not enough.

## Operating Modes

- **Harden** — you are writing or changing code. Apply every rule as you go.
- **Review** — you are auditing existing code. Report findings with severity
  (🔴 panic or corruption, 🟡 latent bug, 🟢 style) and cite file:line.

## 1. The Typed-Nil Interface Trap

A non-nil interface can hold a nil pointer. This is the single most common
source of "impossible" nil checks in Go.

```go
type NotFoundError struct{ ID string }

func (e *NotFoundError) Error() string { return "not found: " + e.ID }

// ❌ Bad — returns a non-nil error even on success
func find(id string) error {
    var err *NotFoundError // typed nil
    if id == "" {
        err = &NotFoundError{ID: id}
    }
    return err // interface is (type=*NotFoundError, value=nil) — NOT nil
}

// ✅ Good — return the untyped nil literal
func find(id string) error {
    if id == "" {
        return &NotFoundError{ID: id}
    }
    return nil
}
```

Rules:

- Never declare a concrete error/pointer variable and return it as an
  interface. Return `nil` explicitly on the success path.
- Never store a possibly-nil concrete pointer in an `error`, `io.Reader`, or
  any interface-typed struct field.
- `staticcheck` reports `SA4023` when a comparison with `nil` can never be
  true because of this trap. No tool catches every case.

## 2. Nil Map, Slice, and Channel Behaviour

Memorise this table — half of these are safe and half panic or hang.

| Operation | nil map | nil slice | nil channel |
|---|---|---|---|
| Read / receive | zero value | index panics | blocks forever |
| Write / send | **panics** | `append` works | blocks forever |
| `len` / `cap` | `0` | `0` | `0` |
| `range` | zero iterations | zero iterations | blocks forever |
| `close` | n/a | n/a | **panics** |

```go
// ✅ A nil slice is a valid empty slice — do not guard append
var out []string
out = append(out, "a")

// ❌ A nil map is read-only
var m map[string]int
m["k"] = 1 // panic: assignment to entry in nil map

// ✅ Initialise every map before writing
m := make(map[string]int)
```

Return nil slices, not `[]T{}`. They marshal identically in JSON for
`encoding/json` when the field is `omitempty`, and cost no allocation.
Return an empty non-nil map only when the caller is documented to write to it.

## 3. Slice Aliasing

A slice is a view. `append` writes through that view whenever capacity
allows, mutating data the caller still owns.

```go
a := []int{1, 2, 3, 4}
b := a[:2]
b = append(b, 99) // ❌ overwrites a[2]; a is now [1 2 99 4]
```

```go
// ✅ Full slice expression caps the view — append must reallocate
b := a[:2:2]
b = append(b, 99) // a is untouched
```

Apply this whenever you hand a subslice to code you do not control, and
whenever a struct field holds a subslice of a larger buffer.

A subslice also keeps the entire backing array alive. To release a large
buffer, copy what you need: `head := slices.Clone(buf[:64])`.

## 4. Defensive Copying at Boundaries

Slices and maps are reference types. Storing or returning one without a copy
hands out a mutable handle to your internals.

```go
type Config struct{ hosts []string }

// ❌ Bad — caller can mutate our state, both ways
func NewConfig(hosts []string) *Config { return &Config{hosts: hosts} }
func (c *Config) Hosts() []string      { return c.hosts }

// ✅ Good — copy in, copy out
func NewConfig(hosts []string) *Config {
    return &Config{hosts: slices.Clone(hosts)}
}
func (c *Config) Hosts() []string { return slices.Clone(c.hosts) }
```

Use `maps.Clone` for maps. Both are shallow — a `[]*User` clone still shares
the pointed-to users.

Copy when the value is retained past the call or exposed to a caller. Do not
copy a slice you only read inside the function; that is wasted allocation.

### Preventing accidental copies

A struct holding a `sync.Mutex`, `sync.WaitGroup` or `atomic.Int64` must
never be copied — a value receiver or by-value parameter gives the copy an
independent lock, and both halves believe they are synchronised. `go vet`'s
`copylocks` catches these; for no-copy types without a lock, embed a
`noCopy` marker so `vet` covers them too. Examples:
`references/copies-and-lifecycle.md`.

## 5. Numeric Conversion and Comparison

Go never panics on numeric conversion. It truncates.

```go
// ❌ Silent corruption when the value does not fit
count := int32(int64Total)

// ✅ Range-check before narrowing
if int64Total > math.MaxInt32 || int64Total < math.MinInt32 {
    return fmt.Errorf("total %d out of int32 range", int64Total)
}
count := int32(int64Total)
```

The same applies to `int` → `uint` (negatives wrap to huge values) and to
`len()` results assigned to sized types. `gosec` reports these as G115.

Never compare floats with `==` — compare against a tolerance. Never compare
`time.Time` with `==` — `Equal` compares the instant, while `==` also
compares the monotonic reading and location. Examples:
`references/numeric-safety.md`.

Integer division by zero panics; float division by zero yields `±Inf` or
`NaN`, and `NaN != NaN`. Guard divisors that come from input.

## 6. Resource Lifecycle

- `defer` runs at **function** return, not at the end of a loop body. A
  `defer f.Close()` inside a loop keeps every file open until the loop
  ends — give each iteration its own function scope. The same applies to
  `resp.Body.Close`, `rows.Close`, `mu.Unlock` and `tx.Rollback`.
- Check the error from a deferred `Close` on anything you wrote to: a
  failed flush on close is silent data loss. Assign it to a named `err`
  result when the body succeeded.

Examples: `references/copies-and-lifecycle.md`.

## 7. Zero-Value and Initialisation Safety

Design types so the zero value works, then no constructor can be forgotten.

A `sync.Mutex` zero value is ready, and a nil map can be read; initialise
the map lazily on first write under the lock. Example:
`references/copies-and-lifecycle.md`.

When lazy init must happen exactly once and may race, use `sync.Once`.

Avoid `init()`. It runs before `main`, cannot fail cleanly, cannot be tested
in isolation, and its cross-file order depends on filenames. Use an explicit
`New...` constructor that returns an error.

## Enforce with Tooling

Run these; do not rely on reading alone. Skip and note any tool that is not
installed.

```bash
go vet ./...                                  # copylocks, lostcancel, printf
golangci-lint run                             # errcheck, bodyclose, makezero, sqlclosecheck
staticcheck ./...                             # SA4023 typed-nil comparisons
gosec -include=G115,G104 ./...                # integer overflow, unhandled errors
go test -race ./...                           # aliasing bugs often surface as races
```

Relevant golangci-lint linters: `errcheck`, `bodyclose`, `sqlclosecheck`,
`rowserrcheck`, `makezero`, `nilerr`, `exhaustive`, and `govet` with the
`nilness` analyzer enabled — `go vet` itself does not run `nilness`.

## Verification Checklist

1. No function returns a concrete pointer type as an interface on a success path
2. Every map is initialised before its first write
3. Subslices handed across a package boundary use a full slice expression `a[:n:n]`
4. Slices and maps stored in or returned from a struct are cloned
5. No type holding a mutex or atomic is passed or received by value
6. Every narrowing numeric conversion is range-checked, or documented as bounded
7. No `==` on floats or on `time.Time`
8. Divisors derived from input are checked against zero
9. No `defer` inside a loop body without an enclosing function scope
10. Deferred `Close` on written resources reports its error
11. `go vet`, `golangci-lint` and `go test -race` are clean

© eduardo-sl · MIT · eduardo-sl/go-agent-skills

Похожие скиллы

Все скиллы по теме «Код-ревью и рефакторинг» →
improve-codebase-architecture Matt Pocock Скилл ищет архитектурные узкие места в кодовой базе и показывает варианты углубления модулей в HTML-отчёте. Подойдёт для ревью перед рефакторингом. 1 163 035установок tdd Matt Pocock Скилл управляет циклом красный-зелёный в TDD: проверяет, что тесты пишутся до кода, держит их на уровне публичных интерфейсов и не завязывает на внутреннюю структуру. 1 141 062установок triage Matt Pocock Скилл проводит issue и внешние PR через роли триажа, проверку и подготовку брифа для агента. Подойдёт мейнтейнеру, который разбирает очередь задач. 990 453установок codebase-design Matt Pocock Скилл помогает проектировать модули с небольшим интерфейсом и скрытой внутри сложностью. Подходит для рефакторинга, тестируемости и поиска чистого seam. 850 344установок code-review Matt Pocock Проверяет изменения от выбранной точки по стандартам репозитория и требованиям задачи. Нужен для ревью ветки, PR или незавершённой работы. 789 521установок implement Matt Pocock Выполняет задачу по спецификации или тикетам, ведёт работу через тесты и завершает её ревью с коммитом. Подходит для задач, где нужен дисциплинированный цикл разработки. 774 700установок

Из того же набора34

go-troubleshooting Скилл ведёт агента по диагностике рантайм-проблем в Go: паники, дедлоки, утечки горутин и памяти, отчёты race detector. Пригодится, когда программа падает или висит, а причина неясна. CX Codex +1 ★ 83звёзд репо go-security-audit Проверяет код на Go на уязвимости OWASP Top 10, инъекции и ошибки в авторизации. Пригодится перед релизом или при аудите пулреквестов. CX Codex +1 ★ 83звёзд репо go-performance-review Скилл ищет узкие места в Go-коде и помогает проверять оптимизации бенчмарками и профилями. Пригодится для ревью горячих путей и снижения аллокаций. CX Codex +1 ★ 83звёзд репо go-observability Скилл для Go-сервисов: помогает настроить структурные логи на log/slog, трейсы и метрики через OpenTelemetry, а также алерты по SLO. Пригодится, когда сервис уже в проде и нужно видеть, что в нём происходит. CX Codex +1 ★ 83звёзд репо go-concurrency-review Скилл проверяет конкурентный код на Go: жизненный цикл горутин, каналы, общие данные и передачу контекста. Пригодится для ревью, поиска гонок и проектирования конвейеров. CX Codex +1 ★ 83звёзд репо go-naming Задаёт правила именования в Go по Effective Go: пакеты, методы, акронимы и ошибки. Помогает избежать заикания имён вроде order.OrderService. CX Codex +1 ★ 83звёзд репо go-modernize Скилл помогает применять современные возможности Go и стандартной библиотеки. Пригодится для точечных правок или модернизации пакета и модуля. CX Codex +1 ★ 83звёзд репо go-error-handling Скилл задаёт агенту правила работы с ошибками в Go: обёртка через %w, sentinel-ошибки, свои типы и errors.Is/errors.As. CX Codex +1 ★ 83звёзд репо

Спонсоры проекта

Компании, которые помогают держать аналитику открытой