Writing Go Code

SkillFiles & storage

Use 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.

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)

  1. Clarity -- purpose and rationale clear to the reader
  2. Simplicity -- simplest way to accomplish the goal
  3. Concision -- high signal-to-noise ratio
  4. Maintainability -- easy to modify correctly
  5. 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: MaxLength not MAX_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() not queue.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 than context.Background() or context.TODO()
  • Avoid global state in tests
  • Tests should provide clear, actionable diagnostics on failure

Linting and Formatting (MANDATORY)

After every change to .go files:

  1. Run dev/lint-fix — runs golangci-lint with --fix to auto-format and fix lint issues
  2. Verify golangci-lint run passes 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

  1. Declare the metric variable (unexported) in the appropriate file:
    • api.go — API-layer metrics (connections, envelope rates, latency)
    • sync.go — node-to-node replication metrics
    • blockchain.go — chain interaction metrics
    • indexer.go, payer.go, dbmetrics.go, migrator.go — domain-specific
  2. Register it in registerCollectors() in metrics.go
  3. Expose it via an exported Emit* function in the same file
  4. Regenerate the metrics catalog: dev/gen/metrics-catalog

Metric type guidance

Use caseType
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 distributionHistogram — 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: _total or _counter
  • Histogram suffix: _seconds, _duration
  • Gauge suffix: _gauge or 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 from pkg/metrics
  • Called at the right point (on success, not on error path, unless counting errors)
  • dev/gen/metrics-catalog run to update doc/metrics_catalog.md

Common Pitfalls

See these references for detailed guidance on frequent Go mistakes:

Signals

GitHub stars
58
Forks
63
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
writing-go-code
Source
github.com/xmtp/xmtpd