Claude Code Go Golang CLAUDE.md AIコーディング golangci-lint 2026

Claude Code × Goプロジェクト: CLAUDE.mdテンプレートとGoモジュールワークフロー(2026年版)

The Prompt Shelf ·

Goは意見の強い言語です。コードフォーマットは一通り、並行処理には慣用パターンがあり、エラーハンドリングは例外ではなく戻り値で行うという規約が型システムによって強制されています。その厳格さこそがGoの大きな強みです。そして同時に、AIコーディングエージェントが明示的な指示なしにGoコードベースを触ると問題が起きやすい理由でもあります。

CLAUDE.md がない状態でClaude CodeがGoプロジェクトで作業すると、次のようなことが起きます。

  • エラーを _ で捨てる(呼び出し元が失敗を知る手段がなくなる)
  • I/O関数に context.Context を渡さない(キャンセルが不可能になる)
  • ロギングに log/slog ではなく fmt.Println を使う
  • ゴルーチンのライフサイクルやキャンセル契約を文書化しない
  • テストを1件ずつ書く(テーブル駆動テストにしない)

いずれもコンパイルエラーにはなりません。技術的には動きます。しかし慣用的なGoではなく、保守性を損なうコードが積み重なっていきます。

なぜGoはCLAUDE.mdが特に重要か

Goの慣習はコードレビューを通じて学ぶものであり、公式仕様に書かれているわけではありません。言語仕様はコンパイルできるものを定義し、熟練Goエンジニアは本番で生き残るパターンを学びます。AIエージェントが欠いているのはまさにその暗黙知です。

エージェントが慣用的なGoから最も逸脱しやすい領域は3つあります。

エラーハンドリング。 Goのエラーハンドリングは明示的です。失敗する可能性のある関数は全てerror値を返します。慣用パターンはそのエラーを即チェックして fmt.Errorf("操作名: %w", err) でコンテキストを付けて返すことです。エージェントはこのラッピングを省略したり、もっと悪い場合には呼び出し元がエラーを期待しているのにlog-and-continueパターンを使ったりします。

コンテキスト伝播。 context.Context はキャンセル・デッドライン・リクエストスコープの値を扱うGoのメカニズムです。I/Oを行う・別サービスを呼ぶ・数ミリ秒以上かかる処理を行う関数では全て第一引数にあるべきです。context.Context を意識せずに生成されたコードはキャンセルできず、本番環境でゴルーチンリークとタイムアウトの連鎖を引き起こします。

ゴルーチンの文書化。 Goは構文的に並行処理が簡単です(go fn())。だからこそ、所有権・ライフサイクル・キャンセルパスが不明確なゴルーチンが生まれやすい。CLAUDE.md でゴルーチン契約の文書化を要求することで、コードレビュー前に問題を浮き彫りにできます。

Go向けCLAUDE.mdの完全テンプレート

このテンプレートはCLIツール・HTTPサービス・ライブラリで実際に検証したものです。プロジェクト固有のセクションを調整してリポジトリルートに配置してください。

# Go Project: [ProjectName]

## Build & Run
- Build: `go build ./...`
- Test: `go test ./... -race`
- Lint: `golangci-lint run`
- Format: `gofmt -s -w .` または `goimports -w .`
- Vet: `go vet ./...`
- Mod tidy: `go mod tidy`

## Goバージョン
- go.modで現在のバージョンを確認する(最低 Go 1.22)
- そのバージョンで利用可能な機能のみ使用する

## エラーハンドリング
- 必ずエラーを返す。本番コードパスで `_` による破棄は禁止
- コンテキストを付けてラップする: `fmt.Errorf("操作名: %w", err)`
- センチネルエラーはエクスポートされた変数で: `var ErrNotFound = errors.New("not found")`
- エラーをログに記録して返すのは禁止。どちらか一方にする

## ロギング
- Go 1.21+ の `log/slog` を使う(`fmt.Println``log.Printf` は禁止)
- 構造化ログ: `slog.Info("メッセージ", "key", value)`
- ライブラリコードではグローバルロガーを使わず `*slog.Logger` パラメータを受け取る

## コンテキスト伝播
- I/Oを行う全関数で `context.Context` を第一引数として受け取る
- contextを構造体のフィールドに格納しない
- `context.WithCancel` または `context.WithTimeout` の直後に必ず `defer cancel()` を呼ぶ
- ライブラリ関数内で `context.Background()` を使わない

## 命名規則
- エクスポートされる名前: CamelCase(`GetUser`, `CreateOrder`, `ErrNotFound`
- 単一メソッドインターフェース: `-er` で終わる(`Reader`, `Writer`, `Storer`
- パッケージ名: 小文字・単語一つ・アンダースコアなし

## テスト
- 常にレースディテクター付きで実行: `go test -race ./...`
- 複数ケースがある関数はテーブル駆動テストを優先
- アサーションには `testify/assert``testify/require` を使用
- 統合テスト: `//go:build integration` ビルドタグで区別
- インターフェース経由でモック: インターフェースを定義してモックする

## ゴルーチン安全性
- 並行使用が安全な型はgodocコメントで明記する
- 共有ミュータブル状態には `sync.Mutex` または `sync.RWMutex` を使用
- ゴルーチン間の協調にはチャネルを優先する
- 全ゴルーチンには明確な終了条件が必要
- ゴルーチンを起動する関数にはライフサイクルをコメントで記述する

## 依存関係管理
- 依存関係の追加・削除後は `go mod tidy` を実行する
- stdlib で十分な場合はサードパーティを避ける
- コンテナ化されたビルドでは `go mod vendor` を使用する
- `go.sum` はバージョン管理にコミットする

## HTTPサービス(該当する場合)
- 標準ライブラリの `net/http` を優先(既にGin/Echo/Chiを使っている場合は除く)
- ミドルウェアのシグネチャ: `func(http.Handler) http.Handler`
- レスポンス: 構造化JSON・明示的なHTTPステータスコード

## インターフェース
- インターフェースは実装パッケージではなく、それを使用するパッケージで定義する
- インターフェースは小さく保つ
- インターフェースを受け取り、具体型を返す

エラーハンドリングルールの詳細

エラーハンドリングセクションはGoのAI関連問題のほとんどが発生する場所です。

強制すべきパターンは明確です。

// Good: コンテキストを付けてラップし返す
result, err := doSomething(ctx)
if err != nil {
    return fmt.Errorf("doSomething: %w", err)
}

// Bad: 破棄
result, _ := doSomething(ctx)

// Bad: ログして続行 — 呼び出し元は失敗を知らない
if err != nil {
    log.Printf("error: %v", err)
    return result, nil // これは嘘
}

3番目のパターン「ログして続行」が最も危険です。コンパイルし、テストを通過し、本番環境でサイレントに障害を飲み込みます。CLAUDE.md には明示的に「エラーをログに記録して返すのは禁止、どちらか一方にする」と書きましょう。

コンテキスト伝播ルールの詳細

コンテキストはGoのAIコーディングで最も重要な落とし穴です。

2つのルールがほとんどの問題を解決します。

第一引数として必ず受け取る。 I/Oを行う・RPCを呼ぶ・データベースを照会する・ブロックする可能性のあるループを実行する全ての関数で、context.Context を第一引数として受け取らなければなりません。

// Good
func FetchUser(ctx context.Context, id string) (*User, error)

// Bad — キャンセルできない
func FetchUser(id string) (*User, error)

構造体に格納しない。 contextはリクエストスコープのデータとデッドラインを持ちます。構造体に格納することはカテゴリエラーです。リクエストのライフタイムをオブジェクトに束縛してしまいます。

// Bad
type UserService struct {
    ctx context.Context // これは禁止
    db  *sql.DB
}

// Good — メソッド呼び出しごとにctxを渡す
func (s *UserService) Get(ctx context.Context, id string) (*User, error)

defer cancel() は3つ目のルールです。context.WithCancelcontext.WithTimeout はリソースを作成します。cancel関数を呼ばないとリークします。

ctx, cancel := context.WithTimeout(parent, 5*time.Second)
defer cancel() // 作成直後に必ず

golangci-lintとフック統合

golangci-lintは複数の静的解析ツールを並列実行します。go vet では捕捉できない問題、特にエラーハンドリングとコンテキストの問題を発見できます。

Claude Codeのフックをファイル編集後に実行するよう設定することで、タイトなフィードバックループが生まれます。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "go vet ./... && golangci-lint run"
          }
        ]
      }
    ]
  }
}

プロジェクトの .claude/settings.json に配置してください。ファイル書き込みのたびにClaude Codeが go vet とgolangci-lintを実行し、失敗した場合は制御を返す前に修正を試みます。

最小限の .golangci.yml:

linters:
  enable:
    - errcheck        # 破棄されたエラーを検出
    - govet           # よくある間違いを検出
    - staticcheck     # 広範囲の静的解析
    - contextcheck    # コンテキスト伝播の欠如を検出
    - noctx           # contextなしのhttpリクエストを検出
    - wrapcheck       # エラーラッピングを強制

linters-settings:
  errcheck:
    check-type-assertions: true
    check-blank: true

contextchecknoctx は前述のコンテキスト伝播問題を具体的にターゲットにしています。CLAUDE.md のルールとlinterを組み合わせることで、指示が見落とすものをlinterが捕捉し、linterが見落とすものを指示が補います。

AGENTS.md互換バージョン

AGENTS.md を読むエージェントに対応するために、簡略版も用意しておきましょう。

# AGENTS.md — Go Project

## コマンド
- test: `go test -race ./...`
- lint: `golangci-lint run`
- format: `goimports -w .`
- vet: `go vet ./...`
- deps: `go mod tidy`

## 必須ルール
1. エラーは常に返す。本番コードで `_` による破棄は禁止
2. エラーをラップする: `fmt.Errorf("context: %w", err)`
3. I/O関数の第一引数は `context.Context`
4. contextを構造体に格納しない
5. cancellationのあるcontextの直後に `defer cancel()`
6. ゴルーチンのライフサイクルをコメントで文書化
7. 複数ケースの関数はテーブル駆動テスト
8. 依存関係変更後は `go mod tidy`

## インターフェースパターン
- インターフェースは実装パッケージではなく使用パッケージで定義する
- 単一メソッドインターフェースは -er サフィックス
- インターフェースを受け取り、具体型を返す

よくあるAI生成Goコードのアンチパターン

CLAUDE.md があっても発生しやすいパターンです。レビュー時に注目してください。

CloseやDefer関数のエラーを無視する。 defer f.Close() でエラーを捕捉しないコードがよく生成されます。書き込み操作では Close() の失敗はデータロスを意味します。

// Bad
defer f.Close()

// Good
defer func() {
    if err := f.Close(); err != nil {
        slog.Error("close failed", "err", err)
    }
}()

HTTPハンドラ内でcontextなしのゴルーチンを起動する。 HTTPハンドラのcontextはクライアントが切断するとキャンセルされます。context.Background() を使ってハンドラ内でゴルーチンを起動すると、リクエスト完了後もゴルーチンが永続します。

init() でグローバル状態を初期化する。 テストが難しくなりインポート順序に依存します。main() またはコンストラクタ関数で明示的に初期化してください。

ミューテックスなしのマップ書き込み。 Goのマップは並行書き込みに安全ではありません。既存の sync.RWMutex を見落として書き込み操作を追加するとレースコンディションが生まれます。レースディテクターが検出しますが、それは並行負荷下の実行時です。


複雑なセットアップは必要ありません。リポジトリルートの CLAUDE.md、4〜5個のlinterを含む .golangci.ymlPostToolUse フックがあれば、Claude CodeのGo出力は「コンパイルするがレビューが必要」から「慣用的でプロダクション品質」へと変わります。

実際のGoプロジェクトからの CLAUDE.md 実例はギャラリーでご覧いただけます。


よくある質問

Claude CodeはGo 1.22の機能を自動的に判断できますか?

go.mod のインデックスはされますが、「理解」とは別です。CLAUDE.md にバージョンの確認を明示することで、Claude Codeが積極的に go.mod を参照して適切な機能を選ぶようになります。

CLAUDE.mdはライブラリパッケージにも置くべきですか?

ほとんどのプロジェクトはルートで十分です。複数のGoモジュールを持つモノレポでは、モジュールディレクトリごとに CLAUDE.md を置く方が、ルート1ファイルで全てを区別しようとするより明確です。

golangci-lintを毎回実行するとClaude Codeが遅くなりませんか?

キャッシュが温まっていれば通常5秒以内です。*.go ファイルにのみトリガーするようフィルタリングすることで、設定ファイル変更時の不要な実行を避けられます。

Go 1.18+のジェネリクスについては?

プロジェクトでジェネリクスを使う場合は CLAUDE.md にセクションを追加してください。最低限、制約パターン(any vs 明示的なインターフェース制約)の好みと、ジェネリック型を専用のサブパッケージに置くか使用箇所と同じパッケージに置くかを指定しましょう。

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules