症状
blog-batch.sh で夜間バッチを回した結果、こんな状態になっていた:
- 同じ wiki ファイル(
content/wiki/concepts/harness-engineering.md、content/wiki/tools/claude-code.md)が複数の blog PR で同時に変更されていた - main にマージしようとすると毎回コンフリクト
- 直近の #368・#369・#371・#372 はすべてこの原因で手動コンフリクト解消が必要だった
調査して原因が判明したので、修正内容と「LLM スキルを書くときに気をつけるべきポイント」を共有する。
PR の中身を見ると 3 コミットあった
通常の /blog で作った PR は「記事 1 本分の 1 コミット」のはずだが、見ると 3 コミットあった:
| |
2 つ目と 3 つ目は wiki ingest の結果コミット。これが各 blog PR に混入していて、複数 PR で同じ wiki ファイルを並行更新 → コンフリクトという流れ。
SKILL.md は「マージ後」と書いてあった
/blog スキルの SKILL.md には、最後にこうある:
| |
人間の読み方では明確に「PR がマージされた後で、20 件以上溜まっていれば wiki ingest を実行する」と書かれている。実装上はバッチ処理を想定すれば、各 PR は人間がレビューしてマージするのが普通だから、wiki ingest は人間のマージ判断の後にしか走らないはず。
しかし実際は、PR 作成中に発火していた。
根本原因: LLM の「単一セッション」と「時系列」のズレ
claude -p "/blog <URL>" という呼び出しは 単一の Claude セッション で完結する。このセッション内で:
- ブログ記事を作成
- ブランチを作って PR を発行
- … ここで本来は「人間がマージするまで待つ」フェーズがあるはず …
- 「Post-merge follow-up」セクションへ進む
/wiki-ingest allを実行- その結果を まだ生きている blog ブランチ にコミット
LLM はマージを待たない。claude -p は同期実行で、関数呼び出しのように上から下までシーケンシャルに進む。「after merge」という人間にとっての時系列指示は、LLM の実行モデルでは 「次のステップ」 とほぼ同義になる。
結果として、/blog セッションは「PR 作成」の直後に「wiki ingest」まで走り抜けてしまい、wiki 変更が blog ブランチに含まれてしまった。
図解
| |
修正内容
1. blog-batch.sh のプロンプトに skip 指示
各 /blog 呼び出しのプロンプトに、必ず次の文言を付与するようにした:
| |
2. --final-wiki-ingest フラグで明示的タイミング制御
バッチ完了後に 1 回だけ /wiki-ingest all を実行するフラグを追加:
| |
ingest のタイミングを バッチ駆動側が支配 することで、blog ブランチへの混入を防ぐ。
3. SKILL.md に skip-condition を明記
Post-merge follow-up の Wiki auto-ingest check 冒頭に次を追加した:
| |
呼び出し元(バッチドライバ)が指示する場合は、/blog 側が必ず先にスキップ判定する設計に変えた。
教訓: LLM スキルに「時系列」を書くときの注意
今回学んだのは、SKILL.md(≒ システムプロンプト)の表現が LLM の実行モデルとズレると、見た目正しいスキルが間違った挙動をするという話。
“after X” 系の表現は危険
| 人間の意図 | LLM の解釈(単一セッション) |
|---|---|
| “after merge” | 「次のステップ」 |
| “後でやる” | 「すぐやる」 |
| “次回セッションで” | 「このセッションで」 |
| “ユーザーがレビューしてから” | (レビュー待ちの概念がないので)スキップしてすぐ次へ |
安全な書き方
- 条件分岐で明示する: 「呼び出し元が X と指示している場合のみ実行」のような明示的なスキップ条件
- タイミングは外部から制御する: ingest のような副作用が大きい処理は、スキル内ではなく バッチドライバ側でタイミングを決める
- 副作用の境界を明確にする: 1 つのスキルが「post 作成」と「wiki 集約」の両方を担うと、副作用の範囲が予測不能になる。役割を分割する
まとめ
| 項目 | 内容 |
|---|---|
| 症状 | 並行 blog PR で同じ wiki ファイルがコンフリクト |
| 直接原因 | wiki ingest コミットが各 blog ブランチに混入 |
| 根本原因 | SKILL.md の “Post-merge” 指示が単一 claude -p セッション内で発火 |
| 修正 | バッチプロンプトに skip 指示 + --final-wiki-ingest フラグ + SKILL.md に skip-condition |
| 教訓 | LLM スキルでは「時系列の後で〇〇する」は成立しない。条件分岐と外部制御で副作用のタイミングを明示せよ |
「Post-merge」のような時系列ベースの指示は、人間のオペレーションを前提にした書き方だと気づきにくい罠だった。Skill 設計時には、**「LLM の実行モデルから見て、この指示はいつ実行されるか」**を必ず想像することが、ハーネスエンジニアリングの一部だと思う。