Guides

Claude Code setup for Go services

Go is friendly to AI coding: the toolchain is fast, formatting is a solved problem, and the test idioms are consistent. Three small pieces make Claude Code reliable in a Go repo: a short CLAUDE.md with the real commands, a hook that formats every edited file, and a standing rule for table-driven tests.

1. CLAUDE.md for a Go service

Put this in ./CLAUDE.md (or ./.claude/CLAUDE.md) and commit it. Running /init will draft a starting file from your codebase; then trim it to what Claude couldn't discover alone. The Claude Code docs suggest keeping each CLAUDE.md under about 200 lines. For general structure, see the CLAUDE.md template.

# orders-service

HTTP/JSON API for order management. Go 1.23, Postgres, deployed as one binary.

## Commands
- Build: `go build ./...`
- All tests: `go test -race ./...`
- One test: `go test ./internal/orders -run TestParseOrder/empty_id -v`
- Vet / lint: `go vet ./...` and `golangci-lint run`
Before saying a task is done: build, vet and `go test -race ./...` must pass.

## Layout
- `cmd/orders/`: main package, wiring only
- `internal/`: all business logic, one package per domain
- `internal/db/gen/`: generated by sqlc, never edit by hand

## Conventions
- Wrap errors with context: `fmt.Errorf("load order %s: %w", id, err)`.
- Pass `context.Context` as the first parameter; never store it in a struct.
- Accept interfaces, return structs. Define interfaces where they are used.
- Tests are table-driven with `t.Run` subtests (see Testing).

## Don't
- Don't add dependencies without asking.
- Don't use `panic` for expected errors.
- Don't ignore returned errors, even in tests.

Adjust the Go version, linter and generated paths to match your repo. Only list commands you have actually run.

2. A hook that runs gofmt after every edit

CLAUDE.md is guidance. A hook is deterministic: Claude Code runs it every time. Per the hooks guide, a PostToolUse hook with an Edit|Write matcher fires after file edits, and the command receives JSON on stdin that includes tool_input.file_path. Add this to .claude/settings.json in the project root:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "f=$(jq -r '.tool_input.file_path'); case \"$f\" in *.go) gofmt -w \"$f\" ;; esac"
          }
        ]
      }
    ]
  }
}

3. Table-driven tests

Table-driven tests are the standard Go idiom, so Claude usually produces them, but you get better results by stating the shape you want. Add this to CLAUDE.md:

## Testing
- Table-driven: a slice of structs with `name`, inputs, and `want`/`wantErr`,
  run with `t.Run(tc.name, ...)`.
- Cover the main case, empty/zero values, boundaries and each error path.
- Use `t.Helper()` in helpers and `t.Parallel()` only when cases share no state.
- Never modify or skip existing tests to make them pass; ask instead.

And a prompt that produces the layout you want:

Write a table-driven test for ParseOrder in internal/orders.
Follow the style of the nearest existing _test.go file. Each case needs a
descriptive name. Include error cases with wantErr. Run
`go test ./internal/orders -run TestParseOrder -v` and show the output.

Reference shape, so you can check Claude's output against it:

func TestParseOrder(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    Order
		wantErr bool
	}{
		{name: "valid", input: `{"id":"a1"}`, want: Order{ID: "a1"}},
		{name: "empty id", input: `{"id":""}`, wantErr: true},
		{name: "bad json", input: `{`, wantErr: true},
	}
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got, err := ParseOrder([]byte(tc.input))
			if (err != nil) != tc.wantErr {
				t.Fatalf("err = %v, wantErr %v", err, tc.wantErr)
			}
			if got != tc.want {
				t.Errorf("got %+v, want %+v", got, tc.want)
			}
		})
	}
}

Quick reference

PieceWhereJob
CLAUDE.md./CLAUDE.mdCommands, layout, conventions
gofmt hook.claude/settings.jsonFormat every edited .go file
Testing rulesCLAUDE.mdTable-driven shape, no weakened tests

Next steps: follow the loop in test-driven development with Claude Code to write the failing table first, then implement.

Skip the setup: get the tested versions

Keelwork bundles 10 workflow skills, 5 tested safety hooks (including a full guard-bash and a secret scanner), 3 subagents and 5 CLAUDE.md templates, with a one-command installer that safely merges into your settings.

Get Keelwork — $24 →