壊れたコードをCIにプッシュするのはもうやめる:Chunkサイドカーをエージェントフックに組み込む
Content Marketing Manager
AIエージェントは、どんな開発者よりも速くコードを書くことができます。しかし、多くのチームでは、フィードバックループがそのスピードに追いついていません。エージェントがコードを生成してCIにプッシュすると、数分後にパイプライン全体の実行結果が返り、単純なリンティングエラーやユニットテストの失敗が発覚します。その時点で、エージェントはすでに次の作業に移っています。動作する状態に戻すには、コンテキストをゼロから再構築し、そもそもリリースされるべきではなかった問題を修正するためだけにトークンを消費することになります。
Chunkサイドカーは、失敗がCIに到達する前に検出することで、このループを短縮します。サイドカーとは、CI環境を再現する軽量なLinuxマイクロVMのことで、テストスイートを数秒で実行し、単純なエラーをエージェント自身のワークフロー内で修正できるようにします。これにより、パイプラインは本来の目的である統合とデプロイの作業のために確保されたままになります。
5分で最初のマイクロビルドを実行するでは、サイドカーのセットアップ方法と、chunk validate を使って検証プロセスを手動でトリガーする方法を学びました。このチュートリアルでは、Chunkのエージェントフックが、手動でのプロンプト入力を必要とせずに、エージェントのワークフロー内の適切なタイミングで同じ検証を自動的にトリガーする方法を紹介します。
エージェントフックで検証ステップを自動化する
エージェントフックとは、ツール呼び出しの前やターンの終了時など、エージェントのループ内の決められたチェックポイントで実行されるスクリプトです。これを使うことで、リンター、フォーマッター、テストスイートといったカスタムの自動化コマンドを組み込み、それらのイベントが発生するたびに自動的に実行させることができます。
Chunkサイドカーはフックを使って、すべての git commit に対する高速なローカルテストゲートと、エージェントが作業を終えようとする直前の環境全体の検証を組み込みます。これにより、エージェントは自身の内部ループの中で小さな失敗を検出し、修正できるようになります。
マイクロビルドのチュートリアルでは、Chunk CLI をインストールし、サンプルプロジェクトをクローンして、chunk auth set circleci と chunk init を実行してワークスペースをセットアップしました。このプロセスでは、コアとなるサイドカー環境を管理する .chunk/config.json が書き込まれるだけでなく、すでに2つのフックが組み込まれた .claude/settings.json も生成されました。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "cmd=$(jq -r '.tool_input.command // empty'); case \"$cmd\" in \"git commit\"*|*\" git commit\"*) cd ${CLAUDE_PROJECT_DIR:-.} && { CI=true npm test; } || exit 2 ;; esac",
"timeout": 300
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "chunk validate",
"timeout": 330
}
]
}
]
}
}
それぞれのフックが発火したときに何が起こるのか見ていきましょう。
2つのフックが行うこと
Chunkのエージェントフックは、エージェントの内部ループを制御するための明確なチェックポイントを確立します。
コミットゲート(PreToolUse)。 このフックは、エージェントがツールを実行する直前に発火します。すべての Bash 呼び出しにマッチし、そのコマンドが git commit かどうかを確認し、そうであればまずテストスイートをローカルで実行します。テストが失敗すると、フックは `exit 2` ステータスを返し、コミットをブロックして出力をエージェントに返します。フィードバックは即座に得られ、エージェントは既存のコンテキストを保ったまま作業を続けられます。
フィニッシュゲート(Stop)。 エージェントがターンを終えようとするとき、Stop フックが介入します。これは chunk validate コマンドを実行し、エージェントのコミットされていない作業ツリーをリモートのサイドカーに同期させ、本番のCI環境を完全に再現したクリーンなLinuxマイクロVM内でテストスイート全体を実行します。検証テストが失敗した場合、フックはコード2で終了し、エージェントのターンを開いたままにして、ミスを修正するもう一度の機会を与えます。この自動リトライループは、.chunk/config.json の stopHook MaxAttempts 設定(デフォルトは3回)によって安全に上限が設けられています。
この2つを組み合わせることで、エージェントがコミットする際の高速なローカルチェックと、終了が許可される前のサイドカー上でのCI相当の検証という、2つのチェックポイントが設けられます。
実際の動作を見る
これらのゲートが実際にどう動作するか見てみましょう。ここでは、エージェントに基本的なタスクを与えたと想像してください。長いカードタイトルを管理するための、truncate(text, maxLength) というシンプルなテキストヘルパー関数を追加するタスクです。
リポジトリのテスト仕様である src/utils/truncate.test.js は、... という省略記号も maxLength に含めてカウントすることを明確に規定しています。つまり、最終的な文字列がその上限を超えることは決して許されません。
エージェントの最初の実装では、文字数をカウントする際に省略記号が無視されています。
export function truncate(text, maxLength) {
if (text.length <= maxLength) return text;
return text.slice(0, maxLength) + '...';
}
このバージョンで truncate(‘hello world’, 8) を呼び出すと、’hello wo…’ が返されますが、これは11文字です。仕様では、出力は8文字を超えてはならないとされています。
次に何が起こるかは、エージェントがどのようにコードを納品しようとするかによって異なります。フックは、高速なローカルチェックと分離されたマイクロVMチェックとで作業を分担しているため、エージェントがどの経路をたどるかによって、ループ内の異なる時点でエラーを検出します。
パターンA:エージェントがコミットする場合
エージェントがこの不完全なコードを書き、すぐに変更をステージしてコミットしようとすると、ローカルチェックが介入します。
$ git add src/utils/truncate.js && git commit -m "Add truncate helper"
コミットゲートは、コミットが実行される前に PreToolUse フックを発火させます。これはツール呼び出しからコマンドを取り出し、git commit であることを確認して、まずテストをローカルで実行します。
PreToolUse フックでの終了コード 2 は、ツール呼び出しを完全にブロックします。Claude Codeはこれを「フックエラー」として表示しますが、実際にはコミットが一切実行されず、出力がエージェントに返されるという結果になります。エージェントは失敗内容を読み取り、slice(0, maxLength - 3) にスライス処理を修正して、再度コミットします。今回はテストが通過し、壊れたバージョンがgitの履歴に残ることはありません。
パターンB:エージェントがターンを終えようとする場合
ローカルチェックはロジックエラーを即座に検出しますが、2つの限界があります。OS固有のバグを見逃すこと、そしてエージェントがコミットコマンドを実行せずにターンをあなたに返そうとした場合には、まったく発火しないことです。ここで、stopフックとChunkサイドカーが内部ループを完結させる役割を果たします。
エージェントが同じ不完全なコードを書き、コミットせずにターンを終えようとすると、Stop フックが発火して chunk validate を実行します。これによりサイドカーが起動し、コミットされていないファイルが同期され、リモート環境でテストスイート全体が実行されます。
[chunk validate]: No active sidecar found, creating a new sandbox...
✓ Created sandbox (72fca210)
Synchronising local → remote: ./workspace/circleci-demo-javascript-react-app … ✓ Synced
running on sidecar: test → CI=true npm test
FAIL src/utils/truncate.test.js
● truncate › counts the ellipsis toward the limit when truncating
Expected: "hello..."
Received: "hello wo..."
● truncate › never returns a string longer than the limit
Expected: "he..."
Received: "hello..."
Tests: 2 failed, 3 passed, 5 total
再び終了コード 2 です。エージェントはサイドカーの出力を読み取り、同じ slice(0, maxLength - 3) の修正を適用し、chunk validate が再び実行されます。結果は5件中5件が成功(green)。ここでようやくターンを終えることができます。
実際には、この2つのゲートは連携して機能することが多くあります。エージェントがコードを書き、コミットを試みると、ローカルチェックが構文エラーやテストの失敗を検出します。それを修正し、コミットに成功します。そしてターンが終わる際に、サイドカーがローカル環境では見逃していた問題を検出します。たとえば、Linux上では異なる動作をする依存関係や、キャッシュされたnode_modulesがないクリーンインストールでのみ失敗するテストなどです。エージェントはそのフィードバックも受け取り、ターンが終了する前に修正します。
CIパイプラインは、統合テストやセキュリティスキャンなど、設定済みのその他のチェックを引き続き実行します。違いは、基本的なユニットテストで失敗していたはずのコードに、そのサイクルを浪費しなくなることです。
Chunkサイドカーフックを他のエージェントで使う
エージェントごとに、それぞれ独自の設定ファイルとイベントがあります。Claude Codeは .claude/settings.json を使用し、Cursorは .cursor/hooks.json を、Codexは .codex/hooks.json を使用し、それぞれ独自のイベント名を持っています。
しかし朗報なのは、chunk validate はごく普通のCLIコマンドにすぎないという点です。chunk init はデフォルトでClaude Codeに自動的にフックを組み込みますが、このワークフローは他のどのエージェントにも簡単にコピー&ペーストできます。該当するエージェントのpre-commitフックまたはstopフックを chunk validate に向けるだけです。サイドカーは、どのエージェントがトリガーしても同じように動作します。
実際のプロジェクトで試す
エージェントとCIパイプラインの間のフィードバックループが、ボトルネックになる必要はありません。2つのフックとサイドカーがあれば、基本的な失敗はマシンの外に出る前に検出され、パイプラインには、より厳しいチェックに備えたコードだけが渡されます。セットアップに必要なのは chunk init コマンド1つだけで、マイクロビルドはすべてのCircleCIプランで完全に無料です。
無料アカウントを取得して、フックを組み込み、壊れたコードをCIにプッシュするのはもうやめましょう。