Skip to main content
This page extends Common Rules with Go specific content.Applies to: **/*.go, **/go.mod, **/go.sum

Coding Style

Formatting

gofmt and goimports are mandatory

No style debates — the Go toolchain enforces formatting
# Format all Go files
gofmt -w .
goimports -w .

Design Principles

Functions should accept interfaces (flexible) and return concrete types (specific).
// GOOD
func ProcessUser(r UserRepository) *User {
  // r is an interface, return value is concrete
}

// AVOID
func ProcessUser(db *Database) UserInterface {
  // Both are too specific/abstract
}
Small, focused interfaces are easier to implement and test.
// GOOD: Small, focused interface
type Reader interface {
  Read(p []byte) (n int, err error)
}

// AVOID: Large interface
type UserManager interface {
  Create(...) error
  Update(...) error
  Delete(...) error
  Find(...) (*User, error)
  List(...) ([]*User, error)
  Validate(...) error
  // ... many more methods
}

Error Handling

Always wrap errors with context:
if err != nil {
    return fmt.Errorf("failed to create user: %w", err)
}
Use %w to wrap errors (Go 1.13+) for error unwrapping with errors.Is and errors.As

Reference

golang-patterns

See skill for comprehensive Go idioms and patterns

Testing

Framework

Use the standard go test with table-driven tests.
func TestCalculateTotal(t *testing.T) {
    tests := []struct {
        name     string
        input    []int
        expected int
    }{
        {"empty slice", []int{}, 0},
        {"single item", []int{5}, 5},
        {"multiple items", []int{1, 2, 3}, 6},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := CalculateTotal(tt.input)
            if result != tt.expected {
                t.Errorf("got %d, want %d", result, tt.expected)
            }
        })
    }
}

Race Detection

Always run with the -race flag:
go test -race ./...
Race conditions can cause subtle, hard-to-debug issues in production. Always test with -race during development.

Coverage

go test -cover ./...

Reference

golang-testing

See skill for detailed Go testing patterns and helpers

Patterns

Functional Options

type Option func(*Server)

func WithPort(port int) Option {
    return func(s *Server) { s.port = port }
}

func NewServer(opts ...Option) *Server {
    s := &Server{port: 8080}
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer(
    WithPort(3000),
    WithTimeout(30*time.Second),
)

Small Interfaces

Define interfaces where they are used, not where they are implemented.
// GOOD: Define interface in consumer package
package service

type UserRepository interface {
    FindByID(id string) (*User, error)
}

type UserService struct {
    repo UserRepository
}

// Implementation in separate package
package postgres

type PostgresRepo struct { ... }

func (r *PostgresRepo) FindByID(id string) (*User, error) { ... }

Dependency Injection

Use constructor functions to inject dependencies:
func NewUserService(repo UserRepository, logger Logger) *UserService {
    return &UserService{repo: repo, logger: logger}
}

Reference

golang-patterns

See skill for comprehensive Go patterns including concurrency, error handling, and package organization

Security

Secret Management

apiKey := os.Getenv("OPENAI_API_KEY")
if apiKey == "" {
    log.Fatal("OPENAI_API_KEY not configured")
}

Security Scanning

Use gosec for static security analysis:
gosec ./...

Context & Timeouts

Always use context.Context for timeout control:
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()

resp, err := client.DoWithContext(ctx, req)
if err != nil {
    return fmt.Errorf("request failed: %w", err)
}
Timeouts prevent goroutines from hanging indefinitely and consuming resources

Hooks

PostToolUse Hooks

Configure in ~/.claude/settings.json:
Automatically runs gofmt and goimports on Go files after edits.
{
  "matcher": "Edit",
  "hooks": [{
    "type": "command",
    "command": "node format-go.js"
  }]
}
Example hook:
const { execFileSync } = require('child_process');

let data = '';
process.stdin.on('data', chunk => data += chunk);
process.stdin.on('end', () => {
  const input = JSON.parse(data);
  const filePath = input.tool_input?.file_path || '';
  
  if (/\.go$/.test(filePath)) {
    try {
      execFileSync('goimports', ['-w', filePath], { stdio: 'pipe' });
    } catch (e) {}
  }
  
  console.log(data);
});
Runs go vet to catch common mistakes.
go vet ./...
Runs staticcheck for more comprehensive analysis.
staticcheck ./...

Agent Support

go-reviewer

Go code review specialist

go-build-resolver

Go build error resolution

Common Rules

Language-agnostic base rules

Hooks Overview

Complete hook system reference