Go language conventions, idioms, and toolchain. Invoke when task involves any interaction with Go code — writing, reviewing, refactoring, debugging, or understanding Go projects.
Applies Go language conventions and toolchain for writing, reviewing, refactoring, or debugging Go code.
npx claudepluginhub xobotyi/cc-foundryThis skill inherits all available tools. When active, it can use any tool Claude has access to.
references/concurrency.mdreferences/errors.mdreferences/gotchas.mdreferences/idioms.mdreferences/structure.mdreferences/testing.mdSimplicity is the highest Go virtue. Resist abstraction until the cost of not abstracting is proven.
Extended examples, code patterns, and detailed rationale for the rules below live in
references/.
| Topic | Reference | Contents |
|---|---|---|
| Naming, declarations, interfaces, receivers, configuration, embedding | idioms.md | Extended code examples for each idiom, Go/bad vs good comparisons, decision criteria tables |
| Variable shadowing, defer traps, slice mutation, strings, copy safety | gotchas.md | Annotated code showing each pitfall with fix patterns, global state examples |
| Error creation, wrapping, Is/As, structured errors (golib/e) | errors.md | Error type decision tree, golib/e API (sentinels, fields, logging), wrapping context examples |
| Goroutines, channels, context, sync, errgroup, data races | concurrency.md | Worker lifecycle patterns, pipeline/fan-out/fan-in code, data race scenarios with fixes |
| Table tests, subtests, assertions, test doubles, benchmarks | testing.md | Full table-test template, testify usage, parallel subtests, httptest/iotest utilities |
| Project layout, packages, imports, file organization | structure.md | Package naming examples, import grouping, backward-incompatible change staged workflow |
Name length scales with scope distance.
| Scope | Style | Examples |
|---|---|---|
| Loop index | Single letter | i, j, k |
| Short function local | 1-3 chars | r (reader), b (buffer), ctx |
| Function parameter | Short but clear | name, path, opts |
| Package-level | Descriptive | defaultTimeout, maxRetries |
| Exported | Self-documenting | ErrNotFound, DefaultClient |
Use 1-2 letter type abbreviation: c for Client, s for Server. Never self, this,
me. Be consistent across all methods of a type.
All-caps for known initialisms: URL, HTTP, ID, API, SQL, XML. In mixed
identifiers: userID, httpClient, xmlHTTPRequest.
user, http, authutil, common, misc, shared, helpers, typeswidget.New() not widget.NewWidget()No Get prefix on getters. Setter uses Set prefix: u.Name() not u.GetName(),
u.SetName(n).
One-method interfaces use method name plus -er: Reader, Writer, Formatter, Stringer.
Honor canonical names — if your type has String() string, call it String, not ToString.
MixedCaps only — never ALL_CAPS or K prefix. Name by role, not value. If a constant has
no role beyond its value, don't define it. const MaxRetries = 12 (good) vs
const Twelve = 12 (bad).
Prefix with _: _defaultPort, _maxRetries. Exception: error values use err prefix:
errNotFound.
widget.New() not widget.NewWidget()var users int not var numUsers int*Project uses Name() not ProjectName()io.Reader (1 method) is more powerful than any
10-method interface.var _ http.Handler = (*Handler)(nil).Use pointer receiver when: method mutates receiver, receiver contains sync.Mutex or
similar, receiver is a large struct, or in doubt (default to pointer).
Use value receiver when: receiver is a small immutable value type (like time.Time),
receiver is a map/func/chan (already reference types), all fields are value types with no
mutability needs.
Never mix receiver types on a single type.
Already reference types. Never use pointers to them: func process(m map[string]int) not
func process(m *map[string]int).
func Foo(ctx context.Context, ...).context.Background() only at the top level — in main() or test setup.context.Context in option structs — pass as separate parameter.var for zero values: var s string, var mu sync.Mutex:= for initializations: s := "hello", n := computeSize()var, omit type if obvious: var _defaultPort = 8080var _e error = myError{}var s []strings := []string{} (nil encodes as
null, empty slice as [])len(s) == 0, not s == nilmake([]T, 0, n)make(map[K]V) for programmatic population; make(map[K]V, n) with capacity hintmap[string]int{"a": 1, "b": 2}var user User&T{} over new(T)Start iota at 1 to distinguish from zero-value (unless zero-value has meaning):
const ( StatusActive Status = iota + 1; ... ).
Use when they disambiguate or document caller obligations. Don't use just to enable naked returns, or when the name repeats the type.
main() calls os.Exit/log.Fatal.defer for cleanup. Always.io.Reader, not filenames. Improves reusability and testability.defer r.Body.Close(), defer rows.Close(),
defer f.Close()._.ErrNotFound.Wrap(err) or e.NewFrom("context", err). Standard fallback:
fmt.Errorf("context: %w", err). Avoid "failed to" prefix in both."read config: open file: permission denied".errors.Is/errors.As — never == or direct type assertion on wrapped errors.| Caller needs to match? | Message | Use |
|---|---|---|
| No | Static | errors.New("not found") |
| No | Dynamic | fmt.Errorf("file %q missing", name) |
| Yes | Static | Exported var ErrNotFound = errors.New(...) |
| Yes | Dynamic | Custom error type with Error() method |
Naming: exported ErrXxx, unexported errXxx. Always wrap sentinels before returning so
callers use errors.Is, not ==.
Naming: exported XxxError, unexported xxxError. Implement Error() string. Callers match
with errors.As.
When a project uses a structured error package like golib/e, prefer it consistently over
fmt.Errorf. See errors.md for API details.
%w wraps (callers can unwrap with errors.Is/errors.As) — default choice%v creates new error with original's text only — use when underlying error is an
implementation detail"get user: %w" not "failed to get user: %w"%w at end of format string so error text mirrors chain structure (newest-to-oldest)errDon't use sentinel return values (-1, "", nil) to signal failure. Return (T, error) or
(T, bool).
MustXYZ panics on error. Legitimate only for package-level initialization and test helpers.
Never use in request handlers or runtime code paths.
Always use comma-ok form: s, ok := val.(string). Never s := val.(string) (panics on
wrong type).
Don't silently ignore errors from deferred calls (f.Close(), rows.Close(),
resp.Body.Close()). Propagate close error if no prior error exists. When intentionally
ignoring, use _ = to make it explicit.
Acceptable only when panics never escape package boundaries and a top-level deferred
recover translates them to errors. Rare — see
errors.md for the full pattern.
Indent errors, keep happy path flat. Early return on error, never nest the happy path in
else blocks.
:= in inner blocks (if/for) silently hides outer variables. The err variable is
commonly shadowed. Use go vet -shadow or golangci-lint to detect. Always verify
assignments in inner blocks use = (not :=) when targeting outer-scope variables.
defer evaluates arguments immediately, not when the deferred function runs. Use closures
to capture current values: defer func() { notify(status) }().
defer runs when the surrounding function returns, not at end of loop iteration. Extract
loop body to a function so defer fires per iteration.
append on a slice with remaining capacity mutates the underlying array. Slices derived
from the same array see each other's writes. Fix: use full slice expression
s[:len(s):len(s)] to cap capacity, or explicit copy.
len(s) returns byte count, not rune count. Use range over string to iterate runes (not
s[i]). Use utf8.RuneCountInString(s) for rune count.
Use strings.Builder with Grow when concatenating in a loop — += is O(n^2). For a few
fixed strings, + or fmt.Sprintf is fine.
sync.Mutex or types containing onePrefer int unless a specific width is required by a protocol, binary format, or
performance constraint. int8, uint16, etc. are prone to silent overflow.
When code does the opposite of what's common (e.g., checking err == nil instead of
err != nil), add a comment to draw attention.
A (*T)(nil) assigned to an interface is non-nil. Return explicit nil when the function
returns an interface type, never a typed nil.
Every goroutine must have: (1) a predictable exit condition, and (2) a way for other code to wait for it to finish. No fire-and-forget goroutines — they leak memory and cause data races.
Use context.Context as the default for all goroutine lifecycle management. Caller creates
context with cancel, goroutine selects on ctx.Done().
Use sync.WaitGroup to wait for multiple goroutines to finish. It handles joining, not
cancellation. Call wg.Add(1) before launching, defer wg.Done() inside the goroutine.
For long-lived goroutines, wrap in a struct with context.Context for cancellation and a
done channel or WaitGroup for joining. Close done channel via defer close(w.done) in
the run method.
When context.Context is unavailable (infrastructure code predating context), use explicit
stop/done channels. Prefer context.Context in new code.
| Relationship | Mechanism | Why |
|---|---|---|
| Parallel goroutines accessing shared state | sync.Mutex | Synchronization |
| Concurrent goroutines coordinating work | Channels | Communication/orchestration |
| Transferring ownership of a resource | Channels | Signaling completion |
Mutexes protect shared state. Channels coordinate independent actors.
<-chan or chan<- in function parameters/returns.Stages connected by channels: each stage receives from upstream, processes, sends downstream.
Close output channel via defer close(out) in the goroutine.
Fan-out: multiple goroutines read from one channel. Fan-in: merge multiple channels into one using a WaitGroup to close the merged channel when all inputs are done.
Limit concurrent work with a fixed worker pool reading from a shared channel.
When multiple cases are ready, select picks one at random — not in source order. For
priority, drain the work channel after receiving the stop signal.
A nil channel blocks forever on send and receive. Set a channel to nil to remove it from
a select at runtime.
Prefer errgroup.WithContext over manual sync.WaitGroup + error collection. It manages
goroutine groups with error propagation and context cancellation. First non-nil error from
any goroutine is returned by g.Wait().
Don't pass HTTP request context to background goroutines — it cancels when the response is
sent. Use context.WithoutCancel(r.Context()) (Go 1.21+) for fire-and-forget background
work.
mu sync.Mutex, never embed. Never copy.
Use defer mu.Unlock() unless nanosecond performance matters.sync/atomic types (atomic.Bool,
atomic.Int64).append isn't data-race-free when slice has spare capacity.
Copy before passing to goroutines.maps.Clone).fmt.Errorf("%v", obj) may call obj.String(), which may
lock the same mutex. Validate before locking, or format with direct field access.init() — spawn in constructors with lifecycle management.select with done/context for cancellable operations.tests, each case ttgive, outputs prefixed wantt.Run with descriptive namesSplit into separate Test... functions when: different cases need different setup/mocking,
conditional assertions inside the loop, complex mock configuration per case, or table fields
used only by some cases.
t.Run creates subtests: t.Fatal stops only the current subtest, run individually with
go test -run=TestX/case, shared setup/teardown via parent function.
Call t.Parallel() in subtests. In Go 1.22+, tt is safe in the closure. For Go < 1.22,
shadow: tt := tt. Group parallel subtests with teardown by nesting under an intermediate
t.Run("group", ...).
require — stops test on failure. Use for error checks and nil guards.assert — reports failure, continues. Use for independent value checks.require.Equal/assert.Equal for struct and slice comparison — never reflect.DeepEqual
directly.assert.ElementsMatch for order-independent slice comparison.errors.Is / errors.As for semantic matchingrequire.ErrorContains for substring when no sentinel availablePrefer t.Error to report all failures at once. Use t.Fatal only for setup failures or
when a check makes subsequent checks impossible.
t.Fatal/t.Fatalf/t.FailNow must only be called from the goroutine running the test
function. Use t.Errorf + return in spawned goroutines. Note: t.Parallel() does NOT
create a new goroutine — t.Fatal is safe in parallel subtests.
Mark with t.Helper() so failures report the caller's line. Don't use t.Helper() in
assert-like wrappers — it hides the connection between failure and cause.
Name by appending test to production package: creditcardtest. Use simple names (Stub,
Fake) when only one type needs doubling; prefix with type name (StubService,
StubStoredValue) when multiple. Prefix test double variables: var spyCC creditcardtest.Spy.
Keep setup scoped to tests that need it. Don't use init() or package-level vars for test
data. Use sync.Once for expensive setup shared across tests.
Go's test cache uses file mtime and env values. Never write to source directory in tests —
use t.TempDir() for temp files and t.Setenv() for environment variables.
Prefer real service instances (databases, caches, brokers) over synthetic mocks. Gate slow tests behind environment variables and skip when not set.
Test_TypeName with underscore for type-level tests, t.Run() for method/scenariopackage foo_test in foo_test.gopackage foo in foo_internal_test.gofoo_benchmark_test.go or foo_benchmark_internal_test.goUse bare blocks {} for logical grouping when separate test reporting is unnecessary. Use
t.Run() when you need parallel execution, selective running, or per-scenario reporting.
Test complementary operations together (Put + Get) when it reduces duplication. Split only when operations have independent failure modes.
Don't assert on serialization output — parse and compare semantically. Never depend on
json.Marshal field ordering.
Write func Example... for complex APIs — godoc renders them, go test verifies them.
The // Output: comment makes the example a test.
Always run tests with -race for concurrent code. Enable in CI. Use //go:build !race to
exclude specific files if needed.
time.Sleep in tests creates flaky tests. Use channels, WaitGroups, or polling with timeout.
If synchronization is impossible, use a retry/poll loop with deadline.
httptest.NewRequest/httptest.NewRecorder for in-process HTTP handler testinghttptest.NewServer for testing clients against fake serverstesting/iotest.ErrReader for error-injecting readerstesting/iotest.OneByteReader for one-byte-at-a-time readsb.Loop() (Go 1.24+) or for i := 0; i < b.N; i++b.ResetTimer() after expensive setupb.ReportAllocs() to track allocations-benchtime=5s or benchstat for stable micro-benchmarksinternal/ for encapsulation. All server logic, supporting packages not part of public
API. Refactor freely without breaking external consumers.cmd/ for commands. Each subdirectory declares package main. Install with
go install .../cmd/tool@latest.userstore not user_storeuser not usersauth, cache, handlerTwo groups separated by blank line: (1) standard library, (2) everything else. Alias only to
avoid conflicts. Blank imports (import _ "pkg") only in main packages or tests. Dot
imports only in test files to resolve circular dependencies.
Within a file, order by: (1) types, constants, variables, (2) constructor (New...),
(3) exported methods grouped by receiver, (4) unexported methods grouped by receiver,
(5) utility functions. Order by rough call order — callers before callees.
foo.go -> foo_test.godoc.go for package-level documentation if neededuser-service.go, http-handler.goStaged workflow: (1) add new code without touching old, (2) migrate callers, (3) remove old code. Each step is a separate commit. Never combine breaking changes with new functionality.
For constructors with 3+ optional parameters, choose between option structs and functional options.
Use when most callers need several options, or options are shared across functions. Benefits:
self-documenting field names, zero-value omission, easy to share and extend. Never include
context.Context in option structs.
Use when most callers need zero options, there are many options, or options require
validation. Use the interface form (type Option interface{ apply(*options) }) over closures
for testability. Options should accept parameters, not use presence as signal:
rpc.FailFast(true) not rpc.EnableFailFast().
| Factor | Option Struct | Functional Options |
|---|---|---|
| Most callers need several options | Prefer | Either |
| Most callers need zero options | Either | Prefer |
| Options need validation | Either | Prefer |
| Options shared across functions | Prefer | Either |
| Third-party extensibility needed | Avoid | Prefer |
Design types so the zero value is immediately useful — no constructor needed. var buf bytes.Buffer is ready to use. Only write constructors when non-zero defaults are required.
Embedding promotes methods of the inner type to the outer type. Use embedding when promoted
methods ARE your intended API. Use named fields when you don't want to expose the inner
type's full method set. Never embed in public API structs unless the promoted surface is
intentional — it commits your API to every exported method including future additions.
Embedding in internal/ types is lower risk.
context.Context as
first parameter for cancellation.any over interface{} (Go 1.18+). Only use any when truly accepting any type.type UserID string adds type safety.
type MyString string adds nothing.Every exported symbol gets a doc comment starting with its name. Complete sentences,
period-terminated. Package comment in doc.go or primary .go file. Unexported types:
comment when behavior is non-obvious, skip when trivial.
Libraries must not force global state. Expose instance-based APIs. Global state is safe only when logically constant, stateless, or has no external side effects. If providing convenience, make the global API a thin proxy to an instance API, and restrict to binaries — never libraries.
When writing Go code: apply all conventions silently — don't narrate each rule. If an existing codebase contradicts a convention, follow the codebase and flag the divergence.
When reviewing Go code: cite the specific violation and show the fix inline. Don't lecture — state what's wrong and how to fix it.
Bad: "According to Go conventions, error strings should be lowercase..."
Good: "errors.New("Not found.") -> errors.New("not found")"
A gopls LSP server is configured for .go files. Always use LSP tools for code navigation
instead of Grep or Glob. LSP understands Go's type system, scope rules, and module
boundaries — text search does not.
| Task | LSP Operation | Why LSP over text search |
|---|---|---|
| Find where a function/type/method is defined | goToDefinition | Resolves imports, aliases, embedded types |
| Find all usages of a symbol | findReferences | Scope-aware, no false positives from string matches |
| Get type signature, docs, or return types | hover | Instant type info without reading source files |
| List all symbols in a file | documentSymbol | Structured output vs grepping for func/type |
| Find a symbol by name across the project | workspaceSymbol | Searches all packages |
| Find concrete types implementing an interface | goToImplementation | Knows the type system and implicit interfaces |
| Find what calls a function | incomingCalls | Precise call graph across module boundaries |
| Find what a function calls | outgoingCalls | Structured dependency map |
Grep/Glob remain appropriate for: text in comments, string literals, log messages, TODO markers, config values, build tags, file name patterns — anything that isn't a Go identifier.
When spawning subagents for Go codebase exploration, instruct them to use LSP tools. Subagents have access to the same LSP server.
golangci-lint: single entry point for formatting and linting. Configure per project.
golangci-lint run — lint. Must pass before committing.golangci-lint fmt — format. Use instead of running gofmt/goimports separately.The coding skill governs workflow (discovery, planning, verification); this skill governs Go implementation choices. Both are active simultaneously.
Simplicity is the highest Go virtue. When in doubt, write boring code.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.