Install
$ agentstack add skill-cicdteam-google-go-style-google-go-style ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Google Go Style — Skill
Derived from the Google Go Style Guide, © Google LLC, licensed CC-BY 3.0. This skill is a derivative digest, not a verbatim reproduction.
Codifies the Google Go Style Guide (canonical + normative + best-practices) into actionable rules.
The full guide is large. This file holds only what should be in your head on every Go change. For deeper rules, load the matching references/*.md. For unusual situations, WebFetch the source — the guide is normative.
Quick Rules
Apply on every line of Go you write or review.
Naming
- No
Getprefix on getters.User(), notGetUser(). Exception: the underlying concept is "get" (HTTP GET). - Don't repeat the package name in identifiers.
bytes.Buffer, notbytes.BytesBuffer.widget.New, notwidget.NewWidget. - Receiver names: 1–2 letters, abbreviation of the type, consistent across all methods. Never
this,self,me, or_(unless unused).func (c *Config), notfunc (config *Config). - Initialisms keep one case.
URL,ID,HTTP,DB. Exported:UserID,ServeHTTP. Unexported:userID,urlPath. NeverUrl,Id,Http. - Test doubles end in
Stub/Fake/Spy/Mock(or describe behaviour:AlwaysCharges,AlwaysDeclines). - No
util/common/helper/modelpackage names. They invite import renames at every call site. Name by what the package provides. - No underscores in identifiers (except
*_test.gotest/benchmark/example names, and packages imported only by generated code). - Local variable scope ↔ name length.
i,c,dbfor tight loops;userCount,pollIntervalfor file scope. Don't drop letters to save typing (Sandbox, notSbx).
Errors
fmt.Errorf("doing X: %w", err)— wrap with%wat the end, so the chain prints newest→oldest asouter: middle: inner.%wonly when callers neederrors.Is/errors.Ason the underlying error. Otherwise use%v.%wat the start is for sentinel wrappers only:fmt.Errorf("%w: invalid header", ErrParse). Category first, details after.- Sentinel name:
ErrFooat package level:var ErrNotFound = errors.New("not found"). - Don't duplicate context the underlying error already carries.
os.Openerrors already include the path —fmt.Errorf("could not open settings.txt: %v", err)is wrong; use a higher-level annotation:fmt.Errorf("launch codes unavailable: %v", err). - Don't add bare "failed: %v" wrappers. They add no information; just
return err. - Either log it or return it — not both. Pick one. Letting the caller log avoids spam.
- Error strings: lowercase, no trailing punctuation, no
\n.fmt.Errorf("something bad happened"), not"Something bad happened.". - Cross-process boundaries (gRPC/RPC): use canonical codes via
status.Errorf(codes.X, ...)rather than wrapping internal errors raw with%w. erroris the last return value. A function takingcontext.Contextshould usually returnerror.
For more, see references/errors.md.
Panics
- Don't panic for normal error handling. Return
errorand multiple return values. log.Fataloverpanicfor terminal conditions inmain/init. Fatal does not run deferred functions; that's the point.- Panics never cross package boundaries in public APIs. Convert to
errorat the API edge with a top-leveldefer recover()that re-panics on unknown payloads. MustXis for package-level vars and tests only.MustParse,template.Must. Not for runtime user input.- Don't
recover()to suppress crashes. Corrupted state propagates further; better is monitoring + crash + fix. panic("unreachable")afterlog.Fatalfis the idiom — the compiler doesn't knowFataldoesn't return.
For more, see references/panics.md.
Tests
- No assertion libraries / helpers (
assert.Equal,require.NotNil). Useif got != want { t.Errorf(...) }. For complex types:cmp.Equal/cmp.Difffromgo-cmp. t.Errorovert.Fatalby default — keep going, report all failures in one run.t.Fatalonly when continuing is meaningless (setup failed, cascading errors would mislead).- Inside
t.Runsubtests: uset.Fatalto skip just that case. Outside subtests in a table loop:t.Error+continue. - NEVER call
t.Fatal(FailNow,Fatalf,SkipNow) from a goroutine other than the test's own. Uset.Errorfrom worker goroutines;t.Fatalonly afterwg.Wait()from the main goroutine. - **Table-driven tests use named struct fields**, not positional:
{name: "empty", input: "", want: ""}. - Test helpers that fail setup call
t.Helper()+t.Fatalf. This makes the failure point to the test line, not the helper line. - Failure message format:
YourFunc(%v) = %v, want %v— function name, inputs, got, want, in that order.
For more, see references/tests.md.
Variables and strings
:=for non-zero init,var x Tfor zero values.i := 42, butvar coords Point(notcoords := Point{}).var t []string, nott := []string{}for empty slices. Empty slice and nil slice behave the same forlen,cap,range,append.new(T)vs&T{}: both are fine for zero values;newreads as "zero value placeholder",&T{}is more common when fields are filled.- Never
==onnilfor slices. Uselen(s) == 0. APIs must not distinguish nil from empty. - Strings:
+for 2–3 short literals;fmt.Sprintffor formatting;strings.Builderin loops;text/templatefor templates. - Pre-size with
make([]T, 0, n)/make(map[K]V, n)only whennis known and the code is performance-sensitive. Default to zero-init. - No shadowing in nested scopes.
if *x { ctx, cancel := ...; }shadowsctxoutside theif. Usectx, cancel = ...with=andvar cancel func()declared above.
For more, see references/strings-and-vars.md.
API design
context.Contextis the first parameter.func F(ctx context.Context, ...) error. Even in test helpers.- Never store
context.Contextin a struct. Pass it through every method that needs it. - No global mutable state. Pass dependencies through constructors / function parameters (DI).
- Take interfaces, return concrete types. The consumer of an interface defines it, not the implementer.
- Channel direction in signatures.
<-chan Tfor receive-only,chan<- Tfor send-only. - Many parameters → option struct (call site reads as labelled fields). 3+ optional / API expected to grow → option struct. Don't put
context.Contextin option structs. - Variadic functional options (
...Option) only when most callers pass nothing, options need parameters, or third parties define options. - Avoid
*string,*io.Readerparameters "to save bytes". Pass values; use pointers for large structs and protobuf messages.
For more, see references/api-design.md.
Documentation
- Doc comments are full sentences starting with the symbol's name.
// Encode writes the JSON encoding of req to w. - Document WHY, not WHAT. The code already says what.
- Document significant sentinel errors and error types the function returns.
- Document cleanup contracts (
Close,Stop, deferred resources). - Document concurrency-safety only when non-obvious (read-only methods are assumed safe; mutating ones are assumed unsafe — say so when that doesn't hold).
- Don't document that
ctx.Donecancels the function — that's the contract ofcontext.Context.
For more, see references/documentation.md.
Package layout
internal/for packages not part of the public API.- Avoid
util/common/helper. Name by domain. - Imports grouped: stdlib / third-party / proto / blank-side-effect. Blank imports only in
mainor tests. - No
import .ever (in Google codebase). - Proto imports get a
pbsuffix:foopb "path/to/foo_go_proto".
For more, see references/package-layout.md.
Decision Matrices
%v vs %w when wrapping an error
| Situation | Choose | Why | |---|---|---| | Caller will errors.Is / errors.As to inspect the chain | %w | Preserves type/sentinel through the chain | | Crossing an external boundary (RPC, IPC, storage); caller wants canonical codes | %v (or status.Errorf) | Don't leak internal error types over the wire | | Logging or human-display only; no programmatic inspection | %v | %w adds chain semantics nobody uses | | Same error is logged here AND returned upward | %v | Wrapping a logged-then-returned error confuses the chain | | Sentinel categorisation first (ErrParse-style) | %w at start: "%w: detail" | Reader sees the category first | | Adding context around a wrapped error (the common case) | %w at end: "context: %w" | Chain prints newest→oldest naturally: outer: middle: inner | | Underlying error already carries this info | nothing — return err | Wrapping without adding info is noise | | Just propagating without analysis | nothing — return err | Don't wrap for the sake of wrapping |
Function arguments: positional vs option struct vs variadic options
| Situation | Choose | Why | |---|---|---| | ≤ 3 parameters, all required, all distinct types | Positional args | Smallest mechanism | | Many parameters, most callers set most of them | Option struct (last param) | Self-documenting field names; grows without breaking call sites | | Many parameters, most callers set none | Variadic options (...Option) | Zero overhead at simple call sites | | Options need failure validation | Variadic options returning error | Can't validate in struct construction | | Third-party packages must define options | Variadic options with exported Option type | Struct fields can't be extended | | Same option set used by multiple functions | Option struct | Reuse + share + write helpers on the struct | | context.Context | First positional arg, never in option struct | Convention |
panic vs log.Fatal vs error return
| Situation | Choose | Why | |---|---|---| | Library detects normal failure | error return | Caller decides | | Library detects an "impossible" invariant violation | error return or log.Fatal | Caller can't recover anyway | | Bad flag/config in main / init | log.Exit (no stack) | Stack trace useless; user wants the message | | Internal package consistency check that has been verified by tests | log.Fatal | More reliable than panic (no defer deadlock risk) | | Parser internals that always have a matching recover at the API edge | panic of a private type | Plumbing errors through deep recursion is noise | | Package-level var initializer needs a value derived from a fallible call | MustX (panics) | Init-time only; init cannot return errors | | HTTP handler crashes mid-request | never recover() to mask it | State is corrupted; let the process crash and restart |
t.Error vs t.Fatal vs t.Errorf + continue
| Situation | Choose | |---|---| | Multiple independent assertions in one Test*, all should run | t.Error / t.Errorf for each | | Setup failure — rest of test cannot proceed | t.Fatal / t.Fatalf | | First failure makes subsequent assertions misleading (e.g. encoded ≠ expected, can't decode meaningfully) | t.Fatalf then continue with t.Errorf | | Table loop without subtests, this case is broken | t.Errorf + continue | | Inside t.Run(...) subtest, this case is broken | t.Fatal (skips this subtest only) | | Worker goroutine inside a test | t.Errorf only (NEVER t.Fatal) | | Test helper called from main test goroutine | t.Helper() + t.Fatalf is fine |
Variable declaration form
| Situation | Choose | Example | |---|---|---| | Initializing with a known non-zero value | := | i := 42 | | Need a zero value, ready for use | var x T | var coords Point, var s []string | | Need a *T to a zero value | new(T) or &T{} | msg := new(pb.Bar) | | Need a *T to a value with fields | &T{...} | c := &Config{Port: 8080} | | Empty slice for return / accumulation | var s []T | not s := []T{} | | Empty map (must be initialized to write) | make(map[K]V) or map[K]V{} | nil map can be read but not written | | Pre-sized slice/map (perf-sensitive, size known) | make([]T, 0, n) / make(map[K]V, n) | Don't over-pre-allocate |
Reference Files
Load on demand:
references/errors.md— detailed error structure, wrapping rules, sentinels, RPC boundaries,errors.Is/As.references/naming.md— receivers, initialisms, repetition, test doubles, util-package antipattern, shadowing.references/api-design.md— option struct vs variadic options, channel direction, DI, interfaces, generics.references/tests.md— Test funcs vs helpers, table-driven, subtests, goroutines, acceptance testing,cmp.references/panics.md— when allowed (invariants, parsers with recover,init), when forbidden,log.Fatalvslog.Exit.references/documentation.md— godoc conventions, what to document, what to skip, contexts, errors, cleanup.references/package-layout.md— package size, file structure, imports,internal/, side-effect imports.references/strings-and-vars.md— concatenation choices, zero values, size hints,newvs&T{}, shadowing.
Authoritative Sources
When in doubt, especially for unusual situations, WebFetch the source section before writing the code. The Google Go Style Guide is the normative authority — this skill is a digest, not a replacement.
- https://google.github.io/styleguide/go/index — overview, normativity definitions
- https://google.github.io/styleguide/go/guide — canonical + normative core principles
- https://google.github.io/styleguide/go/decisions — normative detailed decisions
- https://google.github.io/styleguide/go/best-practices — non-normative patterns and discussions
For Go fundamentals not covered here, read Effective Go.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: cicdteam
- Source: cicdteam/google-go-style
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.