Claude CodeでDaggerのCIを手元に再現し、失敗を直させる手順
DaggerのCheckをdagger checkで手元に再現し、失敗をClaude Codeに直させる手順です。CLAUDE.mdの検証ループ、権限設定、Stop hookでの自動再実行まで書きます。
Claude CodeにCIの失敗を直させるとき、いちばん時間を食うのは「pushして、待って、ログを貼る」往復です。Daggerを使うと、CIで走る検査がdagger checkというコマンドになり、手元で同じ失敗を再現できます。Claude Codeは編集のたびに自分で検査を回し、緑になるまで直せます。
Daggerでは検査が手元のコマンドになる
Daggerは、パイプラインを実コードとコマンドで定義し、pushの前後・手元・クラウドで同じように動かすCIの実行エンジンです。公式ドキュメントはこの仕組みを「スクリプトを、きれいなAPI・実コード・持ち運べるDAG実行エンジンに置き換える」ものと説明しています。
実務で触るのは次の3つです。
- Check: 検査1件。リント、テスト、整形の確認などが当たる
- Generator: 生成物を作る処理。結果は変更セットとして返り、適用前にレビューできる
- Module: CheckやGeneratorを提供する部品。
dagger module installで足す
dagger checkは、ワークスペースの全Checkを並列に走らせ、1つでも落ちると非ゼロで終了します。この「終了コードで合否が決まる」性質が、Claude Codeと相性のよい点です。Claudeはコマンドの終了コードと出力だけを見て、直したかどうかを判断できます。
| やりたいこと | コマンド |
|---|---|
| Checkを一覧する | コマンドdagger check -l |
| 全Checkを実行する | コマンドdagger check |
| 生成物を作る | コマンドdagger generate |
| 関数を直接呼ぶ | コマンドdagger api call <module> ... |
入れて、Checkを一覧にする
Daggerのドキュメントのインストール手順は、バージョンを指定するスクリプトです。手順が指す版はv1.0.0-beta.18で、macOSとLinuxは次のとおりです。
curl -fsSL https://dl.dagger.io/dagger/install.sh | \
DAGGER_VERSION=1.0.0-beta.18 BIN_DIR=$HOME/.local/bin sh
dagger version/usr/local/binに書けない環境では、BIN_DIRをホーム配下にしてPATHに通すか、sudo -E付きで実行します。
次に、プロジェクトのルートでワークスペースを初期化します。
dagger init
dagger module recommend # プロジェクトに合うモジュールを探す
dagger check -l # Checkの一覧dagger initは空のdagger.tomlを作ります。このファイルがワークスペースの設定で、リポジトリを使う全員に共通です。非対話モードのinitは、次に流すコマンドとしてdagger module recommendとdagger cloud checks onを表示します。
推奨モジュールが出なければ、プロジェクトが使うツール名で探します。ESLintなら次のとおりです。
dagger module search eslint
dagger module install dagger.io/js/eslintdagger check -lの*は「モジュールが見つけた全項目」を指します。--allを付けると、Goのパッケージやテストファイルごとに1行ずつ展開されます。--allで行が消えるモジュールは、検査対象を見つけられていません。多くは.golangci.ymlのようなツール側の設定ファイルが無いのが原因です。
失敗したCheckだけを絞って再実行する
Claudeに渡す出力は短いほど、直す精度が上がります。dagger checkは失敗を全部出すのが既定で、最初の失敗で止めたいときは--fail-fastを付けます。
dagger check --fail-fast # 最初の失敗で残りを止める
dagger check --go --check=test # goモジュールのtestだけ
dagger check --go-package=store # 1パッケージ分
dagger check --go-package=store --go-test=TestFixture # テスト1件絞り込みの手がかりは、実行結果に出る行そのものです。ドキュメントのGoの例では、結果が次の形のリンクで報告され、これをdagger checkに渡すと同じ選択をもう一度実行できます。
✔ dag://?check=go/packages/tests/test&go-package=store&go-test=TestFixture 5.3s OKどのフラグで絞れるかが分からないときは、dagger check -l --all --format=cliが行ごとにフラグ形式で出力します。Claudeにこれを実行させれば、失敗した行に対応するフラグを自分で組み立てられます。
CI失敗の手元再現ループ
- 1
全体を回す
dagger check --fail-fastで、落ちるCheckを1つ特定します。 - 2
失敗した行を絞る
結果のリンクか
--format=cliのフラグで、そのCheckだけを再実行します。 - 3
直す
Claudeがコードかテストを編集し、絞った形で再実行します。
- 4
全体で確認する
絞った実行が通ったら、絞り込み無しの
dagger checkで他のCheckを壊していないか確かめます。
CLAUDE.mdに検証ループを書く
ループを毎回プロンプトで説明するより、CLAUDE.mdに置いておく方が安定します。次は書き方の一例です(コマンドは上の節のもの)。
## 検証
- コードを変更したら `dagger check --fail-fast` を実行し、緑になるまで直す
- 失敗したCheckは、結果のリンクか `dagger check -l --all --format=cli`
のフラグで絞って再実行する
- 絞った実行が通ったら、絞り込み無しの `dagger check` で最終確認する
- `dagger.toml` の設定変更と `dagger cloud` 系コマンドは、人の指示があるときだけ行う最後の1行に理由があります。dagger.tomlはリポジトリの全員に効く設定で、モジュールの設定値もここに保存されます。Claudeがテストを通すために設定を緩める方向へ直すと、CIの意味が変わってしまいます。この種の抜け道への対策は、テストだけ通すハードコードを防ぐプロンプトにまとめています。
権限とStop hookで再実行を自動にする
検査コマンドの許可を絞る
毎回の許可ダイアログを避けたい検査コマンドだけを、許可ルールに入れます。Claude Codeの権限ルールは、Bash(npm run *)のようにサブコマンドの後ろに*を置く書き方で、npm run単体にも一致します。dagger checkにも同じ形が使えます。
{
"permissions": {
"allow": ["Bash(dagger check *)"],
"ask": ["Bash(dagger cloud *)", "Bash(dagger api call *)"]
}
}dagger api callを確認に回したのは、Prettierの修正の例がdagger api call -y prettier project --path=. formatのように、変更セットを作業ツリーへ適用する使い方をするからです。dagger cloud系は、アカウントとGitHub連携に関わります。どちらも読み取り専用の検査ではないので、許可リストの外に置く構成です。
終了前に必ず検査を通す
Claudeが「終わりました」と言う前にdagger checkを強制するには、Stopフックを使います。コマンドフックが終了コード2で終わると、Claudeは停止せず、フックのstderrを次の指示として作業を続けます。
.claude/settings.jsonには次のように登録します。
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/dagger-check.sh",
"timeout": 900
}
]
}
]
}
}スクリプトの一例です。
#!/bin/sh
# .claude/hooks/dagger-check.sh
# stdinのJSONで、フック起因の継続中かを判定する
if grep -q '"stop_hook_active": *true'; then
exit 0
fi
out=$(dagger check --fail-fast 2>&1) && exit 0
printf '%s\n' "$out" | tail -n 40 >&2
exit 2stop_hook_activeは、Claude Codeがすでにストップフックの結果として動き続けているときにtrueになる項目です。ここで0を返すと、フックによる差し戻しは1回で終わり、直した後の再検査はしません。直るまで繰り返させたいときはこの分岐を外し、連続継続の上限(8回)に任せる方法もあります。上限の仕組みはStop hookのブロック上限で扱っています。
出力はtailで40行に切っています。全文をClaudeに渡すと、ログの長さがコンテキストを圧迫します。timeoutの既定は600秒なので、テストが長いプロジェクトでは余裕を持たせた値にします。
直せるCheckと、人が決めるCheck
Checkは「落ちる」だけで、直す手段は別に用意されています。ここを取り違えると、Claudeが空回りします。
失敗の種類と直し方
整形の確認(Prettier)
Prettierモジュールを入れると、リポジトリ内のProjectごとに
format-checkが付きます。直すときはdagger api call -y prettier project --path=. formatで整形を適用します。リント(ESLint)
ESLintモジュールは各Projectに
fix関数を持ち、eslint --fixの結果を変更セットで返します。fixは通常の関数でありCheckではないので、dagger checkの中では走りません。生成物のずれ(stale)
Generatorの
staleは、変更セットが空であれば通るCheckです。go generateが追跡中のファイルを変えるなら失敗するため、直すのはdagger generateの結果をレビューして取り込む作業になります。テストの失敗
Goモジュールはテスト1件ごとに結果を出すので、失敗した名前がそのままClaudeへの手がかりになります。
整形やリントの失敗は、機械的な修正コマンドで直る失敗です。Claudeには、まず修正の関数を呼ばせ、残った失敗だけをコードの修正として扱わせると、編集量が減ります。
push後は同じCheckが走る
CIの側は、ドキュメントによればGitHub Actionsの1行で足ります。
- run: dagger checkワークフローを書かずに済ませたいときは、Dagger CloudのCloud Checksがあります。dagger cloud checks onでGitHubリポジトリを連携すると、pushやプルリクエストのたびにCheckが走ります。対象はGitHubのリポジトリで、gitのoriginがそのリポジトリを指している必要があります。実行はdagger activityで確かめられます。ドキュメントは、赤になったら手元でdagger checkを実行して直し、コミットしてpushする流れを案内しています。
この構造の利点は、手元とCIで検査の定義が1つしか無いことです。手元で緑にしたものがCIで落ちる原因は、定義のずれではなく、環境差や外部サービスの状態に絞られます。合否を終了コードでCIに渡す考え方は、plugin evalの結果をCIの合否に使う方法と共通です。
つまずきやすい点
- Goのテストが
SKIPになる: テストがサービスの不在でスキップしても、Checkは失敗にならず通ります。結果にskippedの件数が出ているなら、データベースなどをワークスペースに配線する段階がまだ残っています - フラグが
-で始まる設定値:dagger module settings go goflags -- -tags=extendedのように、値の前に--が要ります。付けないとCLIが値を自分のフラグとして読みます dagger.lock: ワークスペースの設定とは別に、イメージとGitの参照をピン留めするファイルです。dagger.toml、dagger.lock、.daggerはコミットして共有します。Daggerのコマンドが勝手に追記することがあるので、Claudeの変更に含まれていても異常ではありません- 設定の個人差: 個人の設定は
~/.config/dagger/config.tomlに置けます。リポジトリのdagger.tomlを書き換えずに、-gを付けて保存できます
IaCの検証でも同じ発想を使えます。実際の運用に近い題材は、Terraformのplan差分まで検証させる手順にあります。