## 事象 `.github/hooks/hooks.json` ```json { "version": 1, "hooks": { "agentStop": [ { "type": "command", "bash": "bun run format:fix >&2", "timeoutSec": 30, "comment": "Prettierでフォーマット" }, { "type": "command", "bash": ".github/hooks/check.sh typecheck bun run typecheck", "timeoutSec": 60, "comment": "TypeScript型チェック (失敗時はエージェントに修正させる)" }, { "type": "command", "bash": ".github/hooks/check.sh lint bun run lint", "timeoutSec": 30, "comment": "oxlint Lint (失敗時はエージェントに修正させる)" }, { "type": "command", "bash": ".github/hooks/check.sh test bun run test", "timeoutSec": 60, "comment": "Vitestテスト (失敗時はエージェントに修正させる)" } ] } } ``` `.github/hooks/check.sh` ```bash #!/bin/bash # Usage: check.sh <label> <command...> # 成功時: 何も出力しない (allow) # 失敗時: decision=block のJSONをstdoutに出力 LABEL="$1" shift OUTPUT=$("$@" 2>&1) if [ $? -ne 0 ]; then # reason内の改行・特殊文字をJSONセーフにするためjq使用 REASON=$(printf '%s failed:\n%s' "$LABEL" "$OUTPUT" | jq -Rs .) echo "{\"decision\": \"block\", \"reason\": ${REASON}}" fi ``` エージェントのターン終了後に `check.sh` の `echo` が出力されても、それを受け取って[[GitHub Copilot CLI]]が対応をしてくれない。 > [!note] > `agentStop` だと[[プレミアムリクエスト]]を消費するのでそもそもよくないかも。。 ### 環境 | 対象 | バージョン | | ---------------------- | ----- | | [[GitHub Copilot CLI]] | 1.0.5 | ## 原因 仕様通りに実装されていないのではという見解。 ## 解決方法 なし。 ## 参考 > [!left-bubble] ![[chappy-mini.webp]] > 結論から言うと、`format` が動いたことは「フックコマンドが実行された」証拠にはなりますが、「`agentStop` の戻り値が Copilot CLI に正しく解釈されて再ターン化された」証拠にはなりません。そこは別経路です。 > > 見た範囲では、[hooks.json](/Users/tadashi-aikawa/tmp/nuxt4-sandbox/.github/hooks/hooks.json) と [check.sh](/Users/tadashi-aikawa/tmp/nuxt4-sandbox/.github/hooks/check.sh) の作りは概ね正しいです。`check.sh` は失敗時に 1 行 JSON を stdout に出し、exit code 0 で終わるので、GitHub Docs の要件にも合っています。さらに手元の Copilot CLI バンドル [index.js](/Users/tadashi-aikawa/.copilot/pkg/darwin-arm64/0.0.419/index.js) を見ると、`agentStop` は実装されていて、`{"decision":"block","reason":"..."}` を受けたら次のユーザープロンプトとして積み直す処理も入っています。つまり「仕様上はできる」は本当です。 > > ただし、Copilot CLI の hooks 周りは実装がまだかなり不安定です。`format` は単なる副作用なので動いてもおかしくありませんが、`lint/test/typecheck` の自動修正は「stdout JSON を parse する」「`agentStop` として解釈する」「次ターンを起動する」という追加処理が必要で、ここが壊れていると止まります。実際、公開 issue でも hooks の不具合が複数あります。`--resume` で hooks が読まれない [#1503](https://github.com/github/copilot-cli/issues/1503)、`sessionStart` が発火しない [#1730](https://github.com/github/copilot-cli/issues/1730)、hook の stdout が UI に出ない [#1352](https://github.com/github/copilot-cli/issues/1352)、hook 出力を LLM 文脈に一般注入できない [#1139](https://github.com/github/copilot-cli/issues/1139)。2026-03-16 時点では、docs が示す「意図された仕様」に対して実装品質が追いついていない印象です。 > > 次に見るべきなのは debug log です。`copilot --log-level debug --log-dir ./logs` で実行して、失敗時に次のどちらかを確認してください。 > > - `[hook stdout] {"decision":"block","reason":"..."}` > - `Hook execution failed: ...` > > 前者が出ているのに再ターンしないなら、かなり強く Copilot CLI 側の不具合です。後者なら、その hook はスキップされています。`jq` エラー、timeout、実コマンドの exit code 伝播あたりを疑います。もし `copilot --resume=...` 経由で使っているなら、それは別件で hooks 周りが壊れている既知不具合候補です。 > > ソース: > [GitHub Docs: Using hooks](https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/use-hooks) > [GitHub Docs: Hooks configuration](https://docs.github.com/en/copilot/reference/hooks-configuration) > [GitHub Docs: CLI command reference](https://docs.github.com/en/enterprise-cloud@latest/copilot/reference/copilot-cli-reference/cli-command-reference) > [!left-bubble] ![[chappy-mini.webp]] > このログでほぼ原因は見えました。`check.sh` や `hooks.json` の書き方が原因ではありません。 > > まず事実です。 > > - `format` は実行されています。 > - その後に [hooks.json](/Users/tadashi-aikawa/tmp/nuxt4-sandbox/.github/hooks/hooks.json) の `typecheck` と `lint` も実行されています。 > - しかも `typecheck` / `lint` は、Copilot CLI が読むべき JSON を stdout に正しく出しています。 > > 実際にログにはこれが出ています。 > > ```text > [hook stdout] {"decision": "block", "reason": "typecheck failed: ..."} > [hook stdout] {"decision": "block", "reason": "lint failed: ..."} > ``` > > つまり、壊れているのは「hook の実行」でも「JSON の生成」でもなく、その先の「`decision: block` を受けて次ターンを開始する処理」です。 > > 本来ならこのあとに、以前こちらで確認した内部実装どおりなら `pending_messages_modified` 相当の動きや、新しいモデルリクエストが続くはずです。ですがこのログではそこに進んでいません。なので結論はかなり強く、 > > - `agentStop` の block 自体は受理されている > - でも GitHub Copilot CLI 1.0.5 側が再ターン化していない > > です。 > > さらに、このログには次のヒントがあります。 > > - `clientName: "github/cli"` > - `problem.statement: "app.vueの最後に空行を5つ入れて。"` > > これは普通の対話ループというより、`github/cli` クライアント経由の一発実行モードに見えます。推測ですが、このモードでは `agentStop` hook 自体は走る一方で、「次のターンをキューに積み直す」処理が未対応か壊れています。`format` は単なる副作用なので動く、でも自動修正ループだけ動かない、という症状と一致します。 > > 補足すると、`test` が watch でぶら下がっている線は薄いです。[package.json](/Users/tadashi-aikawa/tmp/nuxt4-sandbox/package.json) の `test` は `vitest run` なので、少なくとも watch mode ではありません。 > > 次にやるべき切り分けは 2 つです。 > > 1. 同じ設定を、`github/cli` 経由ではない素の `copilot` 対話セッションで試す > 2. Issue にこのログ断片を添えて報告する > > 特に報告価値が高いのはこの並びです。 > > ```text > Executing hook: .github/hooks/check.sh typecheck ... > [hook stdout] {"decision":"block", ...} > Executing hook: .github/hooks/check.sh lint ... > [hook stdout] {"decision":"block", ...} > ``` > > このあとに再ターンが始まらないので、かなり再現性のある不具合ログになっています。 > > 要するに、「docs 上はいける」は正しいです。ただ、あなたのログでは 2026-03-15 の Copilot CLI 1.0.5 / `github/cli` 実行モードで、その最後の再ターン部分が実際には動いていません。 > [!left-bubble] ![[chappy-mini.webp]] > あります。ただし、2026-03-16 時点では「`agentStop` で `{"decision":"block","reason":"..."}` を返したのに次ターンが始まらない」という完全一致の Issue は見つかりませんでした。近いものは以下です。 > > - [#2013 preToolUse hookSpecificOutput.updatedInput is ignored in Copilot CLI](https://github.com/github/copilot-cli/issues/2013) > 2026-03-13 作成。`1.0.5-0` で hook は実行されるのに、返した `updatedInput` が無視される報告です。今回の「hook stdout には正しい JSON が出ているのに CLI の挙動に反映されない」にかなり近いです。 > - [#1503 Hooks not loaded when session is resumed via --resume](https://github.com/github/copilot-cli/issues/1503) > 2026-02-17 作成。`--resume` 経由だと `.github/hooks/*.json` が読まれない報告です。再開系フローで hooks が壊れている前例です。 > - [#1084 copilot cli --continue can't trigger hooks](https://github.com/github/copilot-cli/issues/1084) > 2026-01-23 作成。`copilot -p ... --continue` で hooks が発火しない報告です。`-p` / 継続系モードで hook 周りが不安定な前例としてかなり関連があります。 > - [#1730 sessionStart hook in .github/hooks/ does not fire in Copilot CLI (v0.0.420)](https://github.com/github/copilot-cli/issues/1730) > 2026-02-28 作成。`sessionStart` 自体が発火しない報告です。hooks 全般の実装品質がまだ怪しいことを示しています。 > - [#991 sessionStart and sessionEnd hooks fire per-prompt instead of per-session in interactive mode](https://github.com/github/copilot-cli/issues/991) > 2026-01-16 作成。hook のライフサイクル解釈が docs と違う報告です。 > - [#1352 sessionStart hook stdout is not displayed in terminal UI](https://github.com/github/copilot-cli/issues/1352) > 2026-02-08 作成。hook の stdout は debug log に出るのに UI に反映されない報告です。これも「hook 出力はあるが CLI の行動に結びつかない」例です。 > - [#1139 Feature Request: Support injecting hook command output into LLM context](https://github.com/github/copilot-cli/issues/1139) > 2026-01-27 作成。bug ではなく feature request ですが、「stdout は捨てられていて文脈に入らない」という認識が共有されています。 > - [#1056 Help me deny git add -A always and forever](https://github.com/github/copilot-cli/issues/1056) > 2026-01-21 作成。恒久的にコマンドを block したい、という要求です。直接の bug ではないですが、「hook や deny 系で CLI の行動を制御したい」文脈として近いです。 > > 補足です。 > > - あなたのログでは `typecheck` / `lint` の hook は実際に実行され、stdout に `{"decision":"block","reason":"..."}` が出ています。なので、今回の本質は「hook が動かない」ではなく「hook の結果が次ターン開始に反映されない」です。 > - その意味で、上の中では特に `#2013` が一番近いです。`hook の戻り値を CLI が無視する` という点が共通しています。 > - `-p` や `--continue` / `--resume` を使っているなら、`#1084` と `#1503` もかなり重要です。 > > 外部情報については、GitHub Issues 以外で有力な Reddit / Discussion / ブログ記事は見つけられませんでした。少なくとも公開情報としては、関連知見の中心は `github/copilot-cli` の Issue 群です。