← 기사 목록
日本語https://zenn.dev/topics/llm/feed

ハーネスは書いて終わりじゃなかった ── 3か月運用して動的に壊れた5つの瞬間

추출된 키워드

31
CLAUDE.md·5ハーネス·5Opus 4.7·4Anthropic·4Claude Code·4subagent·4Hooks·4MCP·4permission allowlist·4ハーネスエンジニアリング·4PreToolUse·3wildcard·3context window·3memory ファイル·3auto-compaction·3Task tool·3PostToolUse·3post-tool-use·3pre-commit·3instruction-following·3context engineering·3Opus 4.6·3GitHub·2anthropics/claude-code·2SmartScope·2CHANGELOG·2リファクタリング·2フィードバックループ·2ハーネスエンジニアリング実践ガイド·2/clear·2/init·2

원문

9,338
ハーネスは書いて終わりじゃなかった ── 3か月運用して動的に壊れた5つの瞬間

ハーネスは書いて終わりじゃなかった ── 3か月運用して動的に壊れた5つの瞬間

書いた瞬間は完璧と思った CLAUDE.md が、3週間後に全部 stale になっていた話

私が初めて自分のハーネスを「これは綺麗にできた」と思ったのは、2026年2月の終わりごろでした。

CLAUDE.md にプロジェクト規約を書き、.claude/hooks に pre-commit と post-tool-use を仕込み、subagent を3つ用意して、permission allowlist もきっちり絞った。git push したあとに自分のリポジトリを見て、満足してコーヒーを淹れた記憶があります。

3週間後、Opus 4.7 がリリースされました。

そこから2か月、私のハーネスは静かに、しかし確実に壊れていきました。動かなくなったわけではない。むしろ表面上は動いている。でも、書いた時に意図した挙動はもう半分くらいしか残っていない。

以前 Zenn で書いた ハーネスエンジニアリング、全員が違うことを言っている では、業界各社の定義のズレを整理しました。今回はその続編というか、もっと地に足のついた話です。「定義が揃ったあと、実際に運用してみたら何が起こるのか」。

前回は設計論。今回は運用論です。3か月で私のハーネスを5回壊した、その瞬間の話をします。

ハーネスが動的に壊れる5つの瞬間

瞬間1: CLAUDE.md がモデル更新で従われなくなる

最初に壊れたのは、CLAUDE.md でした。

Opus 4.6 のときに「コミットメッセージは日本語で書け」「ですます調に統一しろ」と書いていたルールが、4.7 にアップグレードした翌朝、なぜか英語のコミットメッセージが流れ始めた。最初は気のせいかと思いました。

3日後、別のプロジェクトでも同じ症状が出て、ようやく気づきます。CLAUDE.md のルールが、モデル更新で静かに従われなくなる ということに。

これは私の体感だけではありませんでした。GitHub の anthropics/claude-code issues #58369 で「Categorized regression analysis: Opus 4.7」というスレッドが立っていて、世界中のエンジニアが似たような stale 報告を上げていた。Opus 4.7 は 4.6 よりも instruction-following が「リテラル」になった、という Anthropic の説明が出ていますが、現場の感覚は逆です。4.6 で曖昧に拾ってくれていたニュアンスが、4.7 だと拾われなくなった

具体的にはこういうこと。

  • 4.6: 「丁寧に書いてください」と書くと、ですます調に揃えてくれた
  • 4.7: 「丁寧に」の定義が変わり、英語の polite tone と解釈される瞬間がある

しかも、これは確率的に起こる。10回中3回。残り7回は従う。だから「壊れた」とすぐには気づかない。

CLAUDE.md は静的なファイル、と思っていた私が浅はかでした。ハーネスは生き物です。モデルが変わると、同じ文章でも違う意味に解釈される。手書きで書いた契約書を、毎月違う弁護士に読ませているようなものでした。

瞬間2: Hooks の発火順序が静かに変わる

2つ目に壊れたのは、Hooks の発火順序です。

私のハーネスでは PreToolUse でリンターを走らせ、PostToolUse でテストを走らせる構成にしていました。Anthropic 公式ドキュメントに書いてある通りの、教科書的な使い方です。

ところが3月の中旬、ある日突然 pre-commit hook が「skip された」風の挙動を見せ始めました。エラーは出ない。ログにも残らない。でも、明らかに走っていない。

調べた結果、原因はこうでした。subagent を経由した tool 呼び出しのときに、親プロセスの Hook が発火する場合と、しない場合がある。これはモデル側の振る舞いの変化で、CLI 側の設定では制御できない。

Claude Code Hooks: Complete Guide to All 12 Lifecycle Events を読み直して、ようやく理解しました。Hooks には12種類のライフサイクルイベントがあるのですが、subagent 経由かどうかで

PreToolUse
の発火対象が変わる。私はそこを把握せずに hook を組んでいた。

SmartScope の格言を思い出します。

CLAUDE.md に「リンターを実行せよ」と書くのと、Hook でリンター実行を強制するのは「ほぼ毎回」と「例外なく毎回」の違い。

これは真実です。ただし条件があります。Hook が確実に発火する経路に置いた場合に限る。subagent 経由で tool が呼ばれた場合、私のリンター hook は「ほぼ毎回」側に転落していた。蛇口の前のセンサーが、実は別の蛇口にしか反応していなかった。

学んだのは、Hook を仕込んだら、subagent 経由のシナリオもテストする こと。tool 呼び出しを直接実行するパスと、Task tool 経由で subagent に投げるパスは、別のシナリオとして扱わないといけません。

瞬間3: Subagent 委譲時に context が落ちる

3つ目は、Subagent。

私はリサーチ用、コードレビュー用、ドキュメント生成用の3つの subagent を運用していました。それぞれ専用の skill ファイルを持たせ、permission も絞ってあります。

ある日、リサーチ用の subagent に「先週議論した context engineering のアプローチを踏まえて、最新の論文を調査して」と頼んだら、子エージェントが「先週議論した内容について情報がないので、一般的な context engineering の解説をします」と返してきた。

このとき気づきました。Subagent は親の context をほとんど受け取っていない ということに。

Claude Code Agent Teams, Subagents, and MCP: The 2026 Playbook によると、subagent は親とは独立した context window を持ちます。これは「verbose な出力を親の context から隔離するため」という設計思想で、メリットでもある。でも、運用していると 別の顔 が出てきます。

subagent に渡されるのは、Task tool に書いた prompt だけ。親が会話の中で積み上げた前提も、CLAUDE.md の細かい癖も、なにも継承されない。skill ファイルも明示的に preload しない限り、子は読まない。

つまり、親が3時間かけて練り上げた前提を、子は1秒も知らない状態でスタートする

これは設計時には想像していませんでした。subagent = 賢い分身、というイメージで作っていた。実際は subagent = 別の新人エンジニア。毎回オンボーディングし直さないと、文脈ゼロから始まる。

対策はシンプルでした。Task tool に渡す prompt の冒頭に「前提として、このプロジェクトでは X を採用しており、先週 Y という議論があった」を必ず書く。これを書かないと、賢い分身は、初対面の他人になります。

瞬間4: メモリリセットが想定外発火

4つ目はメモリ周りです。これが一番厄介でした。

/clear
コマンドでセッションをリセットする運用は普通だと思います。auto-compaction で context が圧縮されることも、まあわかる。問題は、その2つが 意図せず混在する ことでした。

具体的にはこうでした。長時間セッションを続けていると、Claude Code は内部で context を compaction します。ユーザーには明示的に通知が出る場合と、出ない場合がある。compaction が走ると、それまでの会話の細部が失われる。

私が痛い目を見たのは、リファクタリングの途中。

「このクラスの設計意図はこうで、こういう経緯で命名はこうなって」と最初の30分で説明した内容が、3時間後の最終確認のときに、忽然と消えていました。Claude は「このコードを見る限り、設計意図はおそらく Y です」と勝手に推測を始めて、もとの意図とは違うリファクタを提案してきた。

Claude Code Commands Cheat Sheet (2026) を読むと、

/clear
はメモリをリセットし、
/init
は CLAUDE.md から再構築する、と書いてある。でも、auto-compaction はその中間にあって、ユーザーが完全には制御できない。

これに気づいてから、私は重要な前提を CLAUDE.md と memory ファイルの両方 に書くようにしました。会話で1回伝えただけの情報は、3時間後には存在しないものとして扱う。「メモリは揮発する」という前提で運用する。

私が信長の野望を15年やってきて学んだのは、布石を打つということ。3手先を読むなら、3手分の記録を残しておかないと、AIエージェントは即座に忘れます。

瞬間5: permission allowlist が外部MCP更新で穴開く

5つ目、これが一番ゾッとしました。

私は permission allowlist をかなり厳しく絞っていました。

Bash(git commit:*)
Edit
といった粒度で個別に許可していて、未知のツールはすべて prompt が出る運用です。

3月のある日、外部の MCP サーバーをバージョン更新しました。バージョン 1.2 から 1.3 へのマイナーアップデート。CHANGELOG は読みましたが、特に破壊的変更はない。アップデートして、いつもの作業に戻った。

1週間後、ふと気づきました。新しく追加された MCP tool が、prompt なしで呼ばれている

何が起きていたか。MCP の naming pattern は

mcp__<server-name>__<tool-name>
です。サーバー名が同じなら、新しい tool が追加されてもサーバー単位での allowlist にはマッチします。私は当初
mcp__myserver__*
のようにワイルドカードで許可していた。新しい tool が追加されたら、それも自動で許可される設計だった。

Claude Code MCP permissions 2026: allowedTools vs bypassPermissions では、こう警告されています。

許可は

最も狭い粒度で書くこと。サーバーに10個 tool があって、必要なのが2個なら、その2個だけを書く。

私はサーバー側を信頼していたので、ワイルドカードで済ませた。それが間違いでした。MCP サーバーは自分の管理下にあるとは限りません。サードパーティのMCPだと、メンテナが新機能を追加するたびに、私の allowlist に静かに穴が開いていきます。

これに気づいてから、私は wildcard を全部やめました。tool 単位で明示的に列挙する。MCP サーバーが更新されたら、新しい tool が追加されていないか CHANGELOG を読む。退屈な作業ですが、これ以外に安全な方法はない。

permission allowlist は、書いた瞬間が一番安全で、時間とともに緩んでいく。これも、ハーネスが動的に壊れる典型的なパターンでした。

設計と運用は別物だった

5つの瞬間を並べて見ると、共通している構造があります。

壊れる瞬間トリガー静かさ
CLAUDE.md staleモデル更新エラー出ない
Hooks 順序破綻subagent 経由呼び出しログに残らない
Context 落ちTask tool 委譲子は気づかない
Memory誤発火auto-compactionユーザーに通知なし
Permission穴MCP バージョン更新prompt 出ない

全部に共通するのが「静かに壊れる」こと。

エラーが派手に出てくれれば、まだ気づける。でも、ハーネスの壊れ方は、もっと地味です。昨日と同じように動いているように見えるけど、設計時の意図とはもう違うことをしている。これが一番怖い。

設計と運用は別物でした。設計は静的で、書いた瞬間に完成する。運用は動的で、書いた瞬間から劣化が始まる。

ハーネス本(『ハーネスエンジニアリング実践ガイド』)の第12章で「フィードバックループ」について書きました。即時(秒)、タスク(分)、セッション(時間)、戦略(週月)の4階層。あれは設計時の話です。3か月運用してわかったのは、運用は別の階層が必要ということ。

私のいまの運用ルーチンは、こうなりました。

  • 毎週: CLAUDE.md を頭から読み直し、stale なルールを更新
  • モデル更新時: 全 subagent の prompt を再テスト
  • MCP 更新時: CHANGELOG を読み、allowlist を見直し
  • 重要決定時: その場で memory ファイルに書き込む

退屈です。でも、退屈な作業を仕組み化しない限り、ハーネスは壊れます。ハーネスを書く時間より、メンテする時間のほうが、いまは長い。

まとめ

3か月運用して気づいたこと。

  • ハーネスは静的なファイル群と思いがちだが、実態は動的に劣化するインフラ
  • モデル更新は、CLAUDE.md と subagent prompt を同時に陳腐化させる
  • Hooks の発火条件は、subagent 経由かどうかで変わる。テストは2系統で
  • Subagent は親 context を継承しない。Task tool に毎回オンボーディングを書く
  • Memory は黒板。大事なことは即座にファイルに書き戻す
  • Permission allowlist は wildcard を避け、tool 単位で明示する

書いて終わりじゃなかったんですよね、ハーネスは。書いてから始まる。CLAUDE.md を整えた瞬間より、3か月後に「まだ意図通りに動いているか」を点検する時間のほうが、はるかに価値が高い。

設計者から運用者へ。ここを切り替えられた人だけが、ハーネスエンジニアリングを本当に使いこなせる気がします。面白くいきましょう。

GitHubで編集を提案