Writing Go Code
SkillFiles & storageUse when writing, modifying, or reviewing .go files, implementing new Go functions or packages, or when Go code style and conventions are relevant.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Writing Go Code skill
What this skill tells your AI
The instructions your AI receives, as published by xmtp/xmtpd in .claude/skills/writing-go-code/SKILL.md and read by ahel’s review.
Style Priorities (in order)
- Clarity -- purpose and rationale clear to the reader
- Simplicity -- simplest way to accomplish the goal
- Concision -- high signal-to-noise ratio
- Maintainability -- easy to modify correctly
- Consistency -- consistent with surrounding codebase
Core Rules
- Follow Google Go Style Guide
- Format all code with
golangci-lint fmt - MixedCaps/mixedCaps only -- never snake_case (even constants:
MaxLengthnotMAX_LENGTH) - No fixed line length -- refactor long lines instead of splitting
- Shorter names in Go than other languages; context reduces need for verbosity
- Comments explain why, not what
- Allow code to speak for itself with self-describing symbol names rather than redundant comments
- Use least-powerful mechanism: language primitive > stdlib > external dependency
Formatting
- Imports grouped: stdlib, external, internal (blank line between groups)
- Avoid magic numbers -- use named constants
- No unnecessary levels of abstraction
Naming
- Exported:
PascalCase - Unexported:
camelCase - Short receiver names (1-2 chars matching type initial)
- Acronyms keep case:
HTTPClient,xmlParser - Package names: short, lowercase, no underscores, no
util/common/base - Names should not feel repetitive when used:
queue.New()notqueue.NewQueue() - Predictable names -- a user should be able to predict the name in a given context
Function Design
- Keep functions small and focused
- Prefer composition over embedding
- Use functional options pattern for flexible constructors
- Return concrete types, accept interfaces
- Avoid
init()unless absolutely necessary - Avoid variable shadowing
- Do not over-nest control flow (flatten with early returns)
Testing
- Table-driven tests where appropriate
- Name tests consistently:
TestXxx,BenchmarkXxx - Use
t.Helper()in helper functions - Use
t.Context()rather thancontext.Background()orcontext.TODO() - Avoid global state in tests
- Tests should provide clear, actionable diagnostics on failure
Linting and Formatting (MANDATORY)
After every change to .go files:
- Run
dev/lint-fix— runsgolangci-lintwith--fixto auto-format and fix lint issues - Verify
golangci-lint runpasses with 0 issues
Both must pass before the task is considered done. dev/lint-fix covers gofmt, gofumpt, golines, and all enabled linters.
Metrics
All metrics live in pkg/metrics/. The project uses Prometheus via github.com/prometheus/client_golang.
Adding a new metric
- Declare the metric variable (unexported) in the appropriate file:
api.go— API-layer metrics (connections, envelope rates, latency)sync.go— node-to-node replication metricsblockchain.go— chain interaction metricsindexer.go,payer.go,dbmetrics.go,migrator.go— domain-specific
- Register it in
registerCollectors()inmetrics.go - Expose it via an exported
Emit*function in the same file - Regenerate the metrics catalog:
dev/gen/metrics-catalog
Metric type guidance
| Use case | Type |
|---|---|
| Count of events (requests, envelopes, errors) | Counter — monotonically increasing, use _total suffix |
| Current state (open connections, queue depth) | Gauge — can go up and down |
| Duration / latency distribution | Histogram — use _seconds suffix, observe in seconds |
Naming conventions
- Prefix:
xmtp_<subsystem>_<name>_<type-suffix> - Examples:
xmtp_api_outgoing_envelopes_total,xmtp_sync_messages_received_count - Counter suffix:
_totalor_counter - Histogram suffix:
_seconds,_duration - Gauge suffix:
_gaugeor none
Emit function patterns
// Counter — simple increment
func EmitFoo() { fooTotal.Inc() }
// Counter — batch add
func EmitFoos(n int) { fooTotal.Add(float64(n)) }
// Histogram — observe duration
func EmitFooDuration(d time.Duration) { fooDuration.Observe(d.Seconds()) }
// Gauge with labels — use With()
func EmitBarGauge(label string, v float64) {
barGauge.With(prometheus.Labels{"label": label}).Set(v)
}
Checklist for new metrics
- Variable declared and registered in
metrics.go -
Emit*function exported frompkg/metrics - Called at the right point (on success, not on error path, unless counting errors)
-
dev/gen/metrics-catalogrun to updatedoc/metrics_catalog.md
Common Pitfalls
See these references for detailed guidance on frequent Go mistakes:
- Interfaces and type design -- when designing interfaces, choosing receivers, or structuring types
- Error handling patterns -- when handling, wrapping, or comparing errors
- Concurrency safety -- when writing goroutines, channels, or shared-state code
- Collections and numeric types -- when working with slices, maps, or numeric conversions
Signals
- GitHub stars
- 58
- Forks
- 63
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
writing-go-code- Source
- github.com/xmtp/xmtpd