Use when writing or reviewing Go code - enforces Rob Pike's proverbs, Dave Cheney's Zen, and Google/Uber style patterns. Triggers before any Go implementation or during code review.
Enforces idiomatic Go patterns, error handling, and concurrency best practices during code writing and review.
/plugin marketplace add jamesainslie/go-powers/plugin install jamesainslie-go-powers@jamesainslie/go-powersThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill transforms you into a strict, idiomatic Go programmer who writes and reviews code as if Rob Pike, Dave Cheney, and the Google Go team were watching. It enforces discipline through Go Proverbs as guiding philosophy, with concrete anti-patterns as violations to catch.
When this skill is active, embody these traits:
any unless truly necessary; prefer concrete types or small interfacesreturn err without contextThe Iron Law: Every error must be handled with intent. No error passes silently.
// BAD
if err != nil {
return err
}
// GOOD
if err != nil {
return fmt.Errorf("failed to parse config %s: %w", path, err)
}
%w for wrapping, place at endreturn fmt.Errorf("operation context: %w", err)
var ErrNotFound = errors.New("resource not found")
// Callers use errors.Is
if errors.Is(err, ErrNotFound) { ... }
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string { ... }
// Callers use errors.As
var valErr *ValidationError
if errors.As(err, &valErr) { ... }
// BAD: "Failed to connect to database."
// GOOD: "connect to database"
// BAD
json.Unmarshal(data, &v)
// GOOD
if err := json.Unmarshal(data, &v); err != nil {
return fmt.Errorf("unmarshal response: %w", err)
}
return err without added context_ = someFunc() ignoring returned errorerr.Error() instead of errors.Is/errors.AsThe Iron Law: Accept interfaces, return structs. Define interfaces where they're used, not where they're implemented.
// GOOD - single method, highly reusable
type Reader interface {
Read(p []byte) (n int, err error)
}
// BAD - too many methods, forces large mocks
type UserService interface {
Create(u User) error
Update(u User) error
Delete(id string) error
Find(id string) (User, error)
List() ([]User, error)
// ...10 more methods
}
// In the package that USES the dependency, not the one that implements it
package orderservice
type PaymentProcessor interface {
Charge(amount int) error
}
func NewOrderService(pp PaymentProcessor) *OrderService { ... }
// BAD
func NewClient() ClientInterface { return &client{} }
// GOOD
func NewClient() *Client { return &Client{} }
any/interface{} unless truly generic// BAD - loses type safety
func Process(data any) any
// GOOD - use generics or concrete types
func Process[T Processable](data T) Result
// BAD
func Handle(r *io.Reader)
// GOOD
func Handle(r io.Reader)
| Interface | Package | Methods | Use When |
|---|---|---|---|
Reader | io | Read([]byte) (int, error) | Reading bytes from any source |
Writer | io | Write([]byte) (int, error) | Writing bytes to any destination |
Closer | io | Close() error | Resource cleanup |
ReadWriter | io | Read + Write | Bidirectional byte streams |
ReadCloser | io | Read + Close | Readable resources needing cleanup |
WriteCloser | io | Write + Close | Writable resources needing cleanup |
ReaderAt | io | ReadAt([]byte, int64) (int, error) | Random access reads |
WriterTo | io | WriteTo(Writer) (int64, error) | Efficient copying to writers |
ReaderFrom | io | ReadFrom(Reader) (int64, error) | Efficient copying from readers |
ByteReader | io | ReadByte() (byte, error) | Single byte reads |
ByteScanner | io | ReadByte + UnreadByte | Scanners, parsers |
RuneReader | io | ReadRune() (rune, int, error) | UTF-8 text processing |
Stringer | fmt | String() string | Human-readable representation |
GoStringer | fmt | GoString() string | Debug representation (%#v) |
error | builtin | Error() string | Error values |
Context | context | Multiple | Cancellation, deadlines, values |
Handler | net/http | ServeHTTP(ResponseWriter, *Request) | HTTP request handling |
ResponseWriter | net/http | Header, Write, WriteHeader | HTTP response writing |
RoundTripper | net/http | RoundTrip(*Request) (*Response, error) | HTTP client middleware |
FileInfo | fs | Multiple | File metadata |
FS | fs | Open(string) (File, error) | Filesystem abstraction |
File | fs | Stat, Read, Close | File operations |
Marshaler | encoding/json | MarshalJSON() ([]byte, error) | Custom JSON encoding |
Unmarshaler | encoding/json | UnmarshalJSON([]byte) error | Custom JSON decoding |
Scanner | database/sql | Scan(dest ...any) error | Database row scanning |
driver.Valuer | database/sql/driver | Value() (Value, error) | Custom DB value encoding |
Sort.Interface | sort | Len, Less, Swap | Custom sorting |
Heap.Interface | container/heap | Sort.Interface + Push, Pop | Priority queues |
interface{} where generics or concrete types workThe Iron Law: Before you launch a goroutine, know when it will stop.
// GOOD - context controls lifecycle
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case job := <-jobs:
process(job)
}
}
}
context.Context for cancellation propagation// Always first parameter, named ctx
func DoWork(ctx context.Context, input string) error {
// Check early and often
if err := ctx.Err(); err != nil {
return err
}
// ...
}
// Channels: coordinating goroutines, signaling, pipelines
done := make(chan struct{})
go func() { work(); close(done) }()
<-done
// Mutexes: protecting shared state access
var mu sync.Mutex
mu.Lock()
counter++
mu.Unlock()
sync.WaitGroup for fan-outvar wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(i Item) {
defer wg.Done()
process(i)
}(item)
}
wg.Wait()
// BAD - library forces concurrency
func (c *Client) Fetch() Result {
go c.backgroundRefresh() // caller can't control this
return c.cache
}
// GOOD - caller decides
func (c *Client) Fetch() Result { return c.cache }
func (c *Client) StartBackgroundRefresh(ctx context.Context) { ... }
// Unbuffered: synchronization point (default choice)
ch := make(chan int)
// Buffered: only when you know the bound
ch := make(chan int, workerCount) // justified by known producer/consumer ratio
go func() without shutdown mechanismtime.Sleep for synchronizationctx parameter in long-running functionsselect {} (infinite block) without cancellation caseThe Iron Law: Names are for clarity, not completeness. Packages are for cohesion, not categorization.
// Loop/short-lived: single letter fine
for i, v := range items { ... }
for _, r := range readers { ... }
// Package-level or long-lived: descriptive
var defaultTimeout = 30 * time.Second
// BAD
func GetUser(id string) *User
func DoProcess(data []byte) error
// GOOD
func User(id string) *User // noun = returns thing
func Process(data []byte) error // verb = does action
func (s *Server) Listen() error // not "server" or "self" or "this"
func (c *Client) Do(req *Request) (*Response, error)
func (uh *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
type Reader interface { Read([]byte) (int, error) }
type Processor interface { Process(Item) error }
type Repository interface { Find(id string) (*Entity, error) } // role, not -er
// BAD
user_id, HTTP_Client, parseJSON_Response
// GOOD
userID, HTTPClient, parseJSONResponse
// GOOD
httpServer, HTTPServer, xmlParser, XMLParser, userID, UserID
// BAD
HttpServer, XmlParser, UserId
// GOOD
package user
package http
package config
// BAD
package users // no plural
package httpHelpers // no camelCase
package common // too vague
// BAD
package util
package helpers
package common
package misc
// GOOD - move functions to where they're used or create specific packages
package stringutil // if truly reusable
package timeconv // specific purpose
// BAD
package user
func (u *User) UserName() string // user.UserName()
// GOOD
package user
func (u *User) Name() string // user.Name()
myapp/
├── cmd/
├── internal/ // cannot be imported outside myapp
│ ├── auth/
│ └── cache/
└── pkg/ // public, stable API (optional)
Get/Set prefixes on methodsthis/self as receiver namesutil, helpers, common, misc, baseconfig.ConfigFile, user.UserServiceThe Iron Law: Tests are documentation. They must be clear, deterministic, and independent.
func TestParse(t *testing.T) {
tests := []struct {
name string
input string
want Result
wantErr bool
}{
{
name: "valid input",
input: "42",
want: Result{Value: 42},
},
{
name: "empty input",
input: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Parse(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Parse() = %v, want %v", got, tt.want)
}
})
}
}
t.Run for subtests// Enables: go test -run TestParse/valid_input
t.Run(tt.name, func(t *testing.T) { ... })
t.Helper() for test helpersfunc assertNoError(t *testing.T, err error) {
t.Helper() // Points to caller's line on failure
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
t.Parallel() when tests are independentfunc TestFeature(t *testing.T) {
t.Parallel() // Top-level
tests := []struct{ ... }
for _, tt := range tests {
tt := tt // Capture for parallel (pre-Go 1.22)
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // Subtest-level
// ...
})
}
}
t.Cleanup() for teardownfunc TestWithTempFile(t *testing.T) {
f, err := os.CreateTemp("", "test")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { os.Remove(f.Name()) })
// test continues...
}
// BAD - external dependency, hides what's tested
assert.Equal(t, expected, got)
// GOOD - explicit, clear failure messages
if got != want {
t.Errorf("Calculate() = %v, want %v", got, want)
}
// For complex comparisons, use go-cmp
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
user.go // Implementation
user_test.go // Tests (same package, access internals)
user_export_test.go // Export helpers for external tests
func BenchmarkParse(b *testing.B) {
input := "test input"
b.ResetTimer()
for i := 0; i < b.N; i++ {
Parse(input)
}
}
t.Run for multiple casest.Helper() in helper functionstime.Sleep in tests (use channels or t.Deadline)STOP and reconsider when you notice:
| Red Flag | Violation |
|---|---|
interface{} or any in function signature | "interface{} says nothing" |
| Interface with 5+ methods | "The bigger the interface, the weaker the abstraction" |
panic() outside of init or truly unrecoverable | "Don't panic" |
reflect package import | "Reflection is never clear" |
unsafe package import | "With the unsafe package there are no guarantees" |
_ = someFunc() ignoring error | "Don't just check errors, handle them gracefully" |
return err without wrapping | "Errors are values" - add context |
go func() without shutdown path | "Before you launch a goroutine, know when it will stop" |
time.Sleep for synchronization | Channels orchestrate |
Package named util, common, helpers | Generic names hide purpose |
Get prefix on methods | Go convention: noun for getters |
this or self as receiver | Use 1-2 letter type abbreviation |
| Red Flag | Problem |
|---|---|
init() with side effects (network, file I/O) | Init should only set up package state, not do work |
Variable shadowing with := | Accidentally creates new variable instead of assigning |
context.Background() or context.TODO() in production | Should propagate context from caller |
| Global mutable state | Pass dependencies explicitly |
| Red Flag | Problem |
|---|---|
sync.WaitGroup passed by value | WaitGroup must be passed by pointer |
sync.Mutex copied (embedded in struct passed by value) | Mutex must not be copied after first use |
Single-case select statement | Just receive from channel directly |
select {} without cancellation | Blocks forever with no exit path |
Goroutines in init() | Uncontrolled lifecycle, hard to test |
| Red Flag | Problem |
|---|---|
Naked return in long functions | Unclear what's being returned |
defer inside loops | Defers don't run until function exits; use closure |
Returning -1 or magic values for "not found" | Use (value, bool) or (value, error) |
log.Fatal or os.Exit in library code | Kills caller's process; return error instead |
| Red Flag | Problem |
|---|---|
| Returning slice/map that aliases internal state | Caller can mutate your internals; copy first |
| Appending to slice parameter without copy | May mutate caller's underlying array |
strings.Contains on err.Error() | Use errors.Is or errors.As |
Empty interface{} as map key/value without reason | Loses type safety |
| Red Flag | Problem |
|---|---|
| Circular dependencies between packages | Restructure; use interfaces at boundaries |
//nolint without explanation | Must document why lint is being suppressed |
Missing default in type switch (when appropriate) | May silently ignore new types |
| Exported function without doc comment | Public API must be documented |
// BAD: Ignoring error
data, _ := json.Marshal(v)
// BAD: Bare return
if err != nil {
return err
}
// BAD: String matching
if err.Error() == "not found" { ... }
// BAD: Panic for recoverable error
if user == nil {
panic("user not found")
}
// BAD: Swallowing error in goroutine
go func() {
doWork() // error ignored
}()
// BAD: WaitGroup by value
func process(wg sync.WaitGroup) { // copied!
defer wg.Done()
}
// BAD: Sleep for synchronization
go doWork()
time.Sleep(100 * time.Millisecond)
result := getResult()
// BAD: Defer in loop
for _, f := range files {
file, _ := os.Open(f)
defer file.Close() // won't close until function exits!
}
// BAD: Single-case select
select {
case msg := <-ch:
process(msg)
}
// GOOD: just use
msg := <-ch
process(msg)
// BAD: Mutex copied
type Cache struct {
mu sync.Mutex
data map[string]string
}
func (c Cache) Get(k string) string { // c is copied, including mutex!
c.mu.Lock()
defer c.mu.Unlock()
return c.data[k]
}
// BAD: Interface defined at implementation
package user
type UserRepository interface { ... } // should be in consumer package
type userRepo struct { ... }
// BAD: Returning interface
func NewService() ServiceInterface { return &service{} }
// BAD: Pointer to interface
func Process(r *io.Reader) { ... }
// BAD: God interface
type DatabaseService interface {
CreateUser(...)
UpdateUser(...)
DeleteUser(...)
// ...20 more methods
}
// BAD: Stutter
package user
func NewUserService() *UserService { ... } // user.NewUserService, user.UserService
// BAD: Get prefix
func (u *User) GetName() string { ... }
// BAD: Generic package
package util
package helpers
package common
// BAD: this/self receiver
func (this *Server) Start() { ... }
func (self *Client) Connect() { ... }
// BAD: No subtests
func TestParse(t *testing.T) {
// test case 1
result1, _ := Parse("a")
if result1 != expected1 { t.Error(...) }
// test case 2
result2, _ := Parse("b")
if result2 != expected2 { t.Error(...) }
}
// BAD: Missing t.Helper
func checkError(t *testing.T, err error) {
// missing t.Helper() - stack trace points here, not caller
if err != nil {
t.Fatal(err)
}
}
// BAD: Sleep in tests
func TestAsync(t *testing.T) {
go startWorker()
time.Sleep(time.Second) // flaky!
assertResult(t)
}
Don't believe these thoughts:
| Rationalization | Reality |
|---|---|
| "It's just a quick script" | Scripts become production code. Write it right. |
| "I'll add error handling later" | You won't. Handle errors now. |
| "This interface needs all these methods" | Split it. Callers rarely need everything. |
| "panic is fine here, it'll never happen" | It will happen. Return an error. |
| "reflect makes this so much cleaner" | Reflection hides complexity. Use code generation or concrete types. |
| "I need unsafe for performance" | Profile first. You probably don't. |
| "One global variable won't hurt" | It will. Pass dependencies explicitly. |
| "This goroutine will finish eventually" | "Eventually" means resource leak. Add context cancellation. |
| "The error message is obvious from context" | It's not. Wrap with what operation failed. |
| "Table-driven tests are overkill for this" | They're not. Your future self will add cases. |
| "I'll document it later" | Document now or forever hold your peace. |
| "This clever solution saves lines" | "Clear is better than clever." Write the obvious version. |
When reviewing code, actively hunt for:
init() functions doing too muchCritical: Before reporting any violation, challenge it. Ask these questions for each finding:
| Question | If Yes, Reconsider |
|---|---|
| Is this panic for a programmer error (API misuse) rather than a runtime error? | Panic may be appropriate - like nil pointer dereference |
| Is this panic for truly unrecoverable system failure (entropy gone, out of memory)? | Panic may be appropriate - fail fast |
Is this os.Exit in code whose explicit purpose is process termination? | That's not a library killing the caller - it's doing its job |
Is this init() handling process-level concerns (signals, global registration)? | May be the only correct place for this logic |
| Is this global state effectively constant after initialization? | Documented globals for constants are acceptable |
Is this Get prefix on a package-level function (not a method)? | Convention is softer here - style preference, not bug |
| Is this reflection used for config/serialization where it's industry standard? | Common exception - though code generation is better |
Before finalizing your review, categorize each finding:
| Category | Report As | Example |
|---|---|---|
| Real Bug | 🔴 Critical | Ignored error that loses data |
| Design Smell | 🟠 Consider | Panic that could be error, but context makes it reasonable |
| Style Preference | 🟡 Optional | Get prefix on package function |
| False Positive | ❌ Remove | gracefulexit calling os.Exit |
After applying context, these typically remain as real issues:
_ that could fail in practicereturn err in deep call stacks where context is lostcobra.CheckErr / log.Fatal in code that's not startup initializationThese are commonly flagged but often acceptable:
init() for signal handlers, metrics registration, global loggersos.Exit in packages explicitly designed for process lifecycleGet prefixes (style, not correctness)//nolint explaining they're effectively constantAfter pressure-testing, report findings in three tiers:
## 🔴 Real Issues (Fix These)
[Only items that survived pressure testing as actual bugs or significant design problems]
## 🟡 Consider (Context-Dependent)
[Items where the code might be improved but current approach is defensible]
## ✅ Reviewed & Acceptable
[Patterns that looked like violations but are appropriate for the context]
This prevents false authority - don't report style preferences as critical bugs.
Activates when the user asks about AI prompts, needs prompt templates, wants to search for prompts, or mentions prompts.chat. Use for discovering, retrieving, and improving prompts.
Activates when the user asks about Agent Skills, wants to find reusable AI capabilities, needs to install skills, or mentions skills for Claude. Use for discovering, retrieving, and installing skills.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.