## 事象
`.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 群です。