kid-sid/claude-spellbook/go

Claude Code and agent skill by kid-sid/claude-spellbook

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Use when writing or debugging non-trivial Go — error handling patterns, goroutine/channel design, interface composition, generics, context propagation, or Go-specific idioms like table-driven tests and functional options.

Install

npx vskill@latest install kid-sid/claude-spellbook/go

SKILL.md

Go — Advanced Patterns

Language-level patterns for writing correct, idiomatic, performant Go.

When to Activate

  • Designing error types, wrapping, and sentinel errors
  • Structuring goroutines, channels, and sync primitives correctly
  • Propagating context.Context for cancellation and deadlines
  • Composing interfaces and embedding types
  • Using generics (any, constraints, type parameters) appropriately
  • Writing table-driven tests, benchmarks, or fuzz targets
  • Applying functional options, builder patterns, or the options struct pattern
  • Structuring packages, modules, and internal vs exported APIs

Error Handling

Wrapping and unwrapping
go
import "errors"
// Wrap to add context — preserves the original for errors.Is/As
if err != nil {
return fmt.Errorf("fetch user %d: %w", id, err)
}
// Sentinel errors — compare with errors.Is (not ==)
var ErrNotFound = errors.New("not found")
var ErrUnauthorized = errors.New("unauthorized")
if errors.Is(err, ErrNotFound) {
// handle not found
}
// Custom error type — use errors.As to extract
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error on %s: %s", e.Field, e.Message)
}
var ve *ValidationError
if errors.As(err, &ve) {
log.Printf("invalid field: %s", ve.Field)
}
Multiple return errors
go
// BAD: ignoring errors
data, _ := os.ReadFile("config.json")
// GOOD: always handle
data, err := os.ReadFile("config.json")
if err != nil {
return fmt.Errorf("read config: %w", err)
}
// Return early, keep the happy path unindented
func process(id string) (*Result, error) {
user, err := db.GetUser(id)
if err != nil {
return nil, fmt.Errorf("get user: %w", err)
}
orders, err := db.GetOrders(id)
if err != nil {
return nil, fmt.Errorf("get orders: %w", err)
}
return &Result{User: user, Orders: orders}, nil
}

Goroutines and Channels

Goroutine lifecycle — always have an exit strategy
go
// BAD: goroutine leaks — no way to stop it
go func() {
for {
process()
}
}()
// GOOD: context-driven shutdown
func worker(ctx context.Context, jobs <-chan Job) {
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok {
return // channel closed
}
process(job)
}
}
}
Fan-out / fan-in
go
func fanOut(ctx context.Context, in <-chan int, workers int) <-chan Result {
out := make(chan Result)
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for v := range in {
select {
case out <- compute(v):
case <-ctx.Done():
return
}
}
}()
}
go func() {
wg.Wait()
close(out) // signal downstream that all workers are done
}()
return out
}
Channel patterns
go
// Done channel — broadcast shutdown to many goroutines
done := make(chan struct{})
close(done) // unblocks ALL receivers simultaneously
// Buffered channel as semaphore — limit concurrency
sem := make(chan struct{}, 10)
sem <- struct{}{} // acquire
defer func() { <-sem }() // release
// Pipeline stage
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
defer close(out)
for _, n := range nums {
out <- n
}
}()
return out
}
// Select with default — non-blocking send/receive
select {
case ch <- value:
// sent
default:
// channel full — drop or handle
}
sync primitives
go
// Mutex — protect shared state
type SafeCounter struct {
mu sync.Mutex
count map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.count[key]++
}
// RWMutex — concurrent reads, exclusive writes
type Cache struct {
mu sync.RWMutex
store map[string]string
}
func (c *Cache) Get(k string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.store[k]
return v, ok
}
// sync.Once — initialize exactly once (safe for goroutines)
var instance *DB
var once sync.Once
func GetDB() *DB {
once.Do(func() { instance = connect() })
return instance
}
// sync.WaitGroup — wait for a collection of goroutines
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(i Item) {
defer wg.Done()
process(i)
}(item)
}
wg.Wait()

Context

go
// Always accept context as the first parameter in public functions
func FetchUser(ctx context.Context, id string) (*User, error) { ... }
// Propagate — never store context in structs
type Service struct{ db *DB } // GOOD — context passed per call
func (s *Service) Get(ctx context.Context, id string) (*User, error) { ... }
// Deadline / timeout
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel() // always defer cancel — releases resources even on success
// Cancellation
ctx, cancel := context.WithCancel(context.Background())
go worker(ctx)
cancel() // signal worker to stop
// Value — only for request-scoped data (trace IDs, auth tokens), not config
type ctxKey string
const requestIDKey ctxKey = "requestID"
func WithRequestID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, requestIDKey, id)
}
func RequestID(ctx context.Context) string {
v, _ := ctx.Value(requestIDKey).(string)
return v
}
// Check cancellation in long loops
for _, item := range largeSlice {
if ctx.Err() != nil {
return ctx.Err()
}
process(item)
}

Interfaces and Embedding

Interface design — small, composable
go
// BAD: fat interface — hard to implement, hard to test
type Storage interface {
Get(id string) (*User, error)
Save(u *User) error
Delete(id string) error
List(filter Filter) ([]*User, error)
Search(q string) ([]*User, error)
Count() (int, error)
}
// GOOD: small, focused interfaces
type UserGetter interface {
GetUser(ctx context.Context, id string) (*User, error)
}
type UserSaver interface {
SaveUser(ctx context.Context, u *User) error
}
// Compose only where needed
type UserStore interface {
UserGetter
UserSaver
}
// Accept interfaces, return structs
func NewService(store UserGetter) *Service { ... } // testable
func NewPostgresStore(db *sql.DB) *PostgresStore { ... } // concrete return
Embedding
go
// Struct embedding — promotes fields and methods
type Animal struct{ Name string }
func (a Animal) Speak() string { return a.Name }
type Dog struct {
Animal // promoted: dog.Name, dog.Speak()
Breed string
}
// Interface embedding
type ReadWriter interface {
io.Reader
io.Writer
}
// Embedding to extend without inheriting
type LoggedStore struct {
Store // delegates all Store methods
log *slog.Logger
}
func (ls *LoggedStore) GetUser(ctx context.Context, id string) (*User, error) {
u, err := ls.Store.GetUser(ctx, id) // delegate
ls.log.Info("get user", "id", id, "err", err)
return u, err
}

Functional Options Pattern

Preferred over long constructor signatures or config structs that need zero values to be meaningful.

go
type Server struct {
host string
port int
timeout time.Duration
logger *slog.Logger
}
type Option func(*Server)
func WithPort(p int) Option { return func(s *Server) { s.port = p } }
func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } }
func WithLogger(l *slog.Logger) Option { return func(s *Server) { s.logger = l } }
func NewServer(host string, opts ...Option) *Server {
s := &Server{host: host, port: 8080, timeout: 30 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
srv := NewServer("localhost",
WithPort(9090),
WithTimeout(10*time.Second),
WithLogger(slog.Default()),
)

Generics

go
// Type constraint — built-in or custom
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](items []T) T {
var total T
for _, v := range items {
total += v
}
return total
}
// Generic data structures
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
// Map / Filter helpers
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, fn func(T) bool) []T {
var result []T
for _, v := range slice {
if fn(v) {
result = append(result, v)
}
}
return result
}

HTTP Patterns (net/http)

go
// Handler with dependencies — use a method, not a closure
type Handler struct {
svc *UserService
log *slog.Logger
}
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+ built-in path params
user, err := h.svc.Get(r.Context(), id)
if err != nil {
if errors.Is(err, ErrNotFound) {
http.Error(w, "not found", http.StatusNotFound)
return
}
h.log.Error("get user", "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
// Routing (Go 1.22+)
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", h.GetUser)
mux.HandleFunc("POST /users", h.CreateUser)
// Middleware chaining
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
slog.Info("request", "method", r.Method, "path", r.URL.Path, "dur", time.Since(start))
})
}
srv := &http.Server{
Addr: ":8080",
Handler: logging(mux),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}

Testing

Table-driven tests
go
func TestAdd(t *testing.T) {
cases := []struct {
name string
a, b int
want int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := Add(tc.a, tc.b)
if got != tc.want {
t.Errorf("Add(%d, %d) = %d, want %d", tc.a, tc.b, got, tc.want)
}
})
}
}
Test helpers and cleanup
go
func setupDB(t *testing.T) *sql.DB {
t.Helper() // marks this as a helper — errors show caller's line
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("open db: %v", err)
}
t.Cleanup(func() { db.Close() }) // runs after test, even on failure
return db
}
Benchmarks and fuzz tests
go
func BenchmarkProcess(b *testing.B) {
data := generateData()
b.ResetTimer() // exclude setup time
for i := 0; i < b.N; i++ {
Process(data)
}
}
// Run: go test -bench=. -benchmem
func FuzzParse(f *testing.F) {
f.Add("valid input") // seed corpus
f.Fuzz(func(t *testing.T, s string) {
_, err := Parse(s)
// must not panic — error is fine
_ = err
})
}
// Run: go test -fuzz=FuzzParse

Package and Module Conventions

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
186
Repository forks
15
7-day trend score
26

Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/kid-sid/claude-spellbook/go/badge)](https://verified-skill.com/skills/kid-sid/claude-spellbook/go)

Something wrong with this skill or its page? Tell us.