Claude Codeを信頼するために、Claude Codeを信用しない仕組みを作った話 — 自作ハーネス oneShot v0.48.0
Claude Codeを使って仕事をするようになって、開発スピードは確かに爆速になりました。なんつーか、前から思っていた「オレがもう2~3人いたらいーのになー」が少し実現した感じw
ただ、使えば使うほど気になることも増えてきまして。
「これ確認してから終わってね」とお願いしたことを忘れる。長いセッションの途中で当初の目的からズレていく。前に決めた仕様と違うものを作る。レビューが必要な成果物なのに、本人(?)は自信満々で「完了しました」と報告してくる。
いや、ものすごく便利なんですよ。便利なんだけど、優秀な人間の開発者と同じような信頼の置き方をすると、盛大にハマるのよね。めっちゃよくできる新卒を雇ったけど教育必要ねって感じ。
で、コレ、プロンプトを工夫すれば解決するのかというと、んなこたない。指示を増やせばコンテキスト喰うし、守ってくれる保証が増すわけでもないし。
そんなわけで私がここしばらく投資しているのは、プロンプトそのものよりも、その外側に置く**ハーネス(harness)**です。初期テンプレートを自作して改良を重ねていたら、現在 v0.48.0。48版目になってましたwww
今回は、その中身と、なぜこういう構成にしたのかを書いてみます。
ちなみに最近は仕事の開発だけでなく、Claude Code+Codex CLIを使った開発環境でミニゲームも作っています。作ったゲームは Kenji00’s Game Factory(https://games.kenji00.com/) で公開中。役満麻雀、投球をキャッチャー目線で体験する3Dゲーム、ブラック企業を題材にしたクイズゲームなど、ジャンルもバラバラですw
こういう小さなゲームだけじゃなく半年以上継続しているプロジェクト開発とか進めてると、コードを書く速さ以上に「前のセッションで何を決めたっけ?」「修正のついでに別の挙動まで変わってない?」「終わったと言うけど本当に確認した?」という問題が発生するのが素の環境。今回紹介するハーネスは、そうした課題を少しでもクリアできないかなーと思って開発でも使っている初期設定です。
最初に断っておくと、これは「誰にでも最適なClaude Code設定」ではありません。仕事で継続的にコードを書かせ、調査や設計も任せるときに、自分が許容できない失敗を少しずつ潰してきたものです。
この記事の目次
- そもそもハーネスって何?
- 私が一番困ったのは「できたことにしてしまう」問題
- oneShotの全体構成
- 成果物を監査するまで完了させない
-
/clearしても仕事を続けられるようにする - 仕様書が古くなったことを検知する
- LLMに任せない領域を明確にする
- ハーネス自体の効果を測る
- 並列エージェントとログの扱い
- あえて作り込まなかったこと、残る限界
- まとめ
1. そもそもハーネスって何?
ここでいうハーネスは、Claude Codeに渡すプロンプトだけではなく、ルール、Skills、サブエージェント、Hooks、状態管理ファイル、テストなどを組み合わせた「LLMを働かせるための外側の仕組み」です。
LLMは自然言語の解釈や設計・実装には強い。一方で、「絶対に忘れない」「毎回同じ手順を必ず踏む」「例外なくアクセスを禁止する」みたいな仕事は、そもそもLLMにやらせるべきものなのか?と思うんですよね。
そういうの、昔からプログラムでやってきたわけで。
判断する部分はLLMに任せる。判断させる必要のない部分は従来のコードで制御する。
これがoneShotの基本方針です。
2. 私が一番困ったのは「できたことにしてしまう」問題
LLMに調査を任せたり、仕様書をまとめさせたりすると、見た目はそれっぽい成果物が出てきます。
でも、元資料との照合が不十分だったり、条件を一つ読み落としていたり、引用の根拠が曖昧だったりすることがある。さらに困るのは、それを本人が問題として認識せず「作成しました!」と返してくること。
もちろん「必ず監査して」と書くことはできます。でも、それもまた自然言語の指示でしかない。
だったら
監査していないものが存在する限り、作業を完了できないようにすればいいんじゃない?
という発想になりました。
この考え方が、今のハーネスの中心にあります。
3. oneShotの全体構成
主要な部分を抜き出すと、こんな感じ。
oneShot/
├── CLAUDE.md # 変えたくない基本原則
├── SOURCE_MATERIAL/ # 人間が置く入力資料
├── DOCS/ # 正式採用した成果物
├── specs/ # 現行仕様と索引
├── PROJECT-HISTORY/ # 履歴の保管
├── tests/hook_tests.py # フックの回帰テスト
└── .claude/
├── settings.json # 権限・各種Hooks
├── agents/ # 監査・調査・執筆などの担当
├── rules/ # 対象に応じて読み込む規約
├── skills/ # audit / cleanup / resume / measure など
├── hooks/ # Pythonで実装した機械的な制御
├── artifacts/ # まだ正式採用していない成果物
├── CURRENT.md # 現在の状態
├── NEXT-PROMPT.md # 次にやること
├── DECISIONS/ # 意思決定の記録
└── tasks/ # TODOと学習した教訓
ここでポイントなのは、CLAUDE.md に何でも詰め込まないこと。
常時必要な原則は CLAUDE.md に。ある種類のファイルを扱うときだけ必要な規約は rules/ に。明確な手順がある処理は skills/ に。そして
「守ってね」ではなく「守らないと通れません」にしたい部分は hooks/ に置く。
あと、サブエージェントは何でもかんでも呼ぶわけじゃなくて調査や長い文書のドラフトなど、メインのコンテキストを汚しやすい処理だけに使います。簡単な修正まで委譲すると、オーバーヘッドの方が大きくなるので。
要するに、構造を複雑にすることが目的ではないのです。
4. 成果物を監査するまで完了させない
4-1. PostToolUseで未監査の成果物を記録する
oneShotでは、Claudeが .claude/artifacts/ 以下に成果物を作成・更新したことを、PostToolUse のフックで検知します。
該当する成果物は .claude/.pending-audit に記録。これは単なるチェックリストではなく、追加・解除のイベントを積む方式です。
で、ターン終了時に Stop フックがその記録を確認します。
# stop_gate.py の主要部分を簡略化した例
entries = [rel for _, rel in pending_live(root)]
if entries:
reason = (
"未監査のアーティファクトがあります: "
+ ", ".join(entries)
+ "。/audit を実行してください。"
)
print(json.dumps({"decision": "block", "reason": reason}))
未監査の成果物が残っていたら、
「完了」の時点でブロックする。ここが肝です。
「監査を忘れないで」というお願いから、「監査を通らないと通常の完了経路を進めない」というワークフローへ変えました。
4-2. 監査担当は作成担当と分ける
監査自体は /audit Skillから専用の auditor エージェントに渡します。
チェックするのは、おおむね次の5点。
- 要求や成果物契約に適合しているか
- 定めたワークフローを守っているか
- 文書の構造や表現に問題がないか
- 事実確認の根拠は妥当か
- 機密情報が混入していないか
わざわざ作成側の会話や言い訳を渡さず、成果物そのものと監査規約を中心に評価させるのもポイントです。作った本人に自分の仕事を自己採点させると、どうしても甘くなりやすいので。
判定が pass または warn ならゲート解除、fail なら修正して再監査。なお warn は警告付きで通過する扱いなので、無条件の品質保証という意味ではありません。
4-3. 監査済みと正式採用は別
監査を通ったものが、そのまま正式なドキュメントになるわけでもありません。
.claude/artifacts/ にある成果物を、必要に応じて /promote で DOCS/ に昇格させます。
作った、監査した、正式に採用した。この3つを区別する。
人間の開発チームなら当たり前にやっていることですが、LLMに仕事を任せ始めると、ここがごっちゃになりやすいのよね。
ここまでのまとめ
この監査ゲートが保証するのは「成果物が必ず正しい」ことではありません。監査対象の成果物を、無監査のまま通常の完了処理で見過ごしにくくすることです。品質の判断は依然として誤り得ますが、監査という工程そのものの実施忘れは減らせます。
5. /clear しても仕事を続けられるようにする
次に重要なのがコンテキスト管理。
Claude Codeを長時間使っていると、コンテキストが肥大化します。圧縮に頼ることもできますが、そもそも会話履歴の中にしか重要な判断が残っていないのは設計として不安です。
私の場合、ある程度作業が進んだら /cleanup で現在の状態をファイルに書き出し、/clear して次のセッションへ進む運用を前提にしています。
保存するのは例えば、現在の作業焦点、未完了タスク、決定事項、次に実行する指示など。
.claude/CURRENT.md 現在どういう状態なのか
.claude/tasks/todo.md 何が残っているのか
.claude/DECISIONS/ なぜその判断をしたのか
.claude/NEXT-PROMPT.md 次に何をすべきか
PROJECT-HISTORY/ ここまでに何をしたのか
そして新しいセッションの開始時には SessionStart フックが必要な状態や警告を注入。必要なら /resume で作業を引き継ぎます。
これで毎回長大な会話履歴を読ませなくても、ある程度安定した再開ができるようになります。
ここで大切なのは、過去の作業履歴と現在の正しい状態を分けていること。
履歴を全部読み返して現在の仕様を推測させる方式では、古い情報を拾ってくることがあります。だから現行状態は現行状態として別ファイルを持たせています。
ただし /cleanup は自動実行ではありません。忘れた場合を検出・支援する機構はありますが、完全に無操作で引き継ぎが保証されるという話ではないです。
6. 仕様書が古くなったことを検知する
これも地味ですが、気に入っている部分。
LLMに仕様書を書かせても、その後ソースコードを変更すれば仕様書が古くなります。当たり前です。
問題は、仕様書があることで逆に安心してしまうこと。数週間後のセッションで、古い仕様を「正」として実装を進められたら困る。
そこで specs/ 以下の仕様には、根拠にしたソースファイルを sources: で宣言させています。ハーネスが参照元のハッシュを記録しておき、内容が変更されればドリフトを通知する仕組みです。
もちろんハッシュが変わったからといって、即座に仕様が誤っているとは限りません。逆に、ハッシュが変わっていないことが仕様の正しさを証明するわけでもない。
ただ、
「この仕様書を書いたときと実装の状態が違いますよ」
というシグナルは出せる。
これだけでも長期プロジェクトではかなり意味があると思っています。
7. LLMに任せない領域を明確にする
7-1. 入力資料や機密情報はルールだけで守らない
ディレクトリごとに所有者と役割を決めています。
| 場所 | 役割 |
|---|---|
SOURCE_MATERIAL/ |
人間が渡す入力資料。原則読み取り専用 |
.claude/artifacts/ |
Claudeが生成する作業成果物 |
DOCS/ |
正式採用した出力 |
specs/ |
現行仕様 |
.env |
人間が管理する機密設定。Claudeは読まない |
settings.json の permissions に書くのはもちろんですが、それだけに頼らず PreToolUse の protect_paths.py で書き込み先を検査しています。
Write や Edit だけでなく Bash も検査対象です。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|NotebookEdit|Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/protect_paths.py\"",
"onFailure": "block"
}
]
}
]
}
}
これは配布設定の該当部分です。
とはいえ、Bashの内容を静的に調べて全てのファイル操作を正確に把握するのは(改善したいんですけど)困難です。動的に生成されるパスや、別プログラムの内部で行われる操作まで完全に防げるわけではありません。
だから、これを万能のサンドボックスと勘違いしてはいけない。実際の機密情報の隔離やOSレベルの権限設計は別途必要です。
7-2. 依頼と関係ない変更をさせない
CLAUDE.md では、変更範囲を依頼に直接結びつけることも原則にしています。
「ついでに綺麗にしておきました」って、頼んでないファイルを大量に触られるのは、保守案件では本当に困るんですよねw
必要なところだけ変更する。作業完了はテストやログなど観測可能な証拠で示す。分からない部分は「未確認」とする。
これはフックで完全強制しているわけではなく、LLMに守らせる基本規則です。機械的に検証できることと、判断を必要とすることは別扱いにしているわけです。
8. ハーネス自体の効果を測る
設定を48版まで改良してくると、別の問題が出ます。
「その変更、ホントに良くなったの?」
という問題です。
プロンプトやルールを追加すると、なんとなく改善した気になりやすい。でも、結果としてコンテキストが増えただけかもしれない。別の種類の失敗が増えているかもしれない。
そこで /measure を用意しています。
監査の結果、ゲート回避、セッションの状態、変更の記録など、ハーネスが把握できる項目を集計し、改善施策のベースラインと判定結果を残す仕組みです。
イメージとしては、次のような運用。
- 現状の問題を決める
- 測定できる指標を選ぶ
- 変更前の値を記録する
- ハーネスを変更する
- 一定期間運用して比較する
- 採用するか撤回するかを判断する
実際にやってみて一番意外だったのは、ルールを足したときではなく削ったときの結果でした。
常時読み込みしている基本原則のうち、rules/ やフック側が全文を持っている説明の重複を 1,370字ぶん削除しました。そのうえで、同じ評価ケース11本を、削除前のルールと削除後のルールで、同じ日にそれぞれ3回ずつ流して比較してみたんです。
結果は、判定が下がったケースがゼロ。むしろ1本は「部分的に達成」から「達成」に上がりました。
要するにその1,370字は、毎ターン読ませていたのに何も効いていなかったということです。
指示って、増やせば増やすほど効くような気がしてしまうのよね。でも実際には、薄まる方が先に来ていた。これは測らなければ絶対に気づけなかったです。
ここで注意したいのは、測定機構があることと、その設定の有効性が証明されたことは別だという点です。監査通過率だけで品質が分かるわけでもないし、測る対象によっては数字にしない判断も必要です。
それでも、「良くなった気がする」を「何がどう変わったか」に近づけることはできる。
LLMを使って成果物を作る時間と、LLMを使った開発プロセスそのものを信頼できるものにする時間が、最近は同じくらいになっている気がしますwww
9. 並列エージェントとログの扱い
Claude Codeのサブエージェントを複数走らせるなら、状態ファイルの競合も考えないといけません。
例えば、2つのエージェントが同時に成果物を作り、両方が「未監査一覧」を読み込んで編集して保存すると、片方の更新が消える可能性があります。典型的なlost updateです。
そこで未監査の記録は、全件を読み込んで書き戻すのではなく、追加や解除を追記するイベントログ方式にしています。現行の未監査集合は、ログを読み直して算出します。
付属の検証記録には、20並列×20試行で取りこぼしゼロという結果もあります。ただし、これが全環境・あらゆる同時実行条件での保証になるわけではありません。
監査結果や意思決定の記録も、できるだけ過去を書き換えずに残す方向です。後で「どうしてこの判断をしたんだっけ?」を追えるようにするため。
そしてフック自体にも tests/hook_tests.py という回帰テストがあります。現在 643件。これが全部通ることを確認してから版を上げています。
実際、索引の生成方式を変えたときに、エージェントの雛形ファイルだけ書き換えが漏れていたことがありました。しかもテスト側がファイルを名前で開くループになっていたせいで、その雛形だけが検査の外。静かに腐っていたわけです。
ハーネスもコードである以上、ハーネスの変更でハーネスが壊れるということは普通に起きます。ここを気合いで乗り切るつもりはありませんw
10. あえて作り込まなかったこと、残る限界
ここまで読むとずいぶん厳格な仕組みに見えると思いますが、万能ではありません。
まず監査ゲートが直接対象にしているのは、主として .claude/artifacts/ 以下の成果物です。すべてのソースコード変更に対して独立監査を強制するCIではありません。ソースコード側のテストやレビューは別に必要で
ただ人間がそれをやる前にCodex CLIに投げて一次テスト・レビューさせる仕組みが入ってますけれども
さらに、Stop フックにもClaude Code本体側の連続ブロック上限があります。ハーネスにも緊急回避の SKIP_AUDIT_GATE=1 があり、使用した場合はログに残す設計です。
つまり、技術的に絶対抜けられない門を作ったわけではない。
ただし、回避できることと、回避した事実が見えないことは別です。私は例外的な逃げ道は必要だと思っています。その代わり「いつ、どの程度使われたか」を見られるようにしたい。
そして、LLMが実施する監査は、当然ながら誤判定します。第三者的なエージェントに渡したからといって、同じ種類のモデルが持つ限界を超越できるわけではありません。
さらに、監査、記録、状態復元には追加の処理時間やトークンコストもかかります。小さな使い捨てスクリプトまで同じ重装備で開発するのが合理的とも思いません。
oneShotが狙っているのは、継続的に開発する仕事で、後から取り返しのつかない手戻りを減らすことです。
その意味では、仕組みを複雑にしすぎないことも重要で、現在の CLAUDE.md には「部品を増やしたことで、本当に問題がよく解決できるようになったか?」を問う原則も入れています。
ハーネスを作ること自体が目的になったら本末転倒ですからね。
11. まとめ — 信頼できるのは「賢いモデル」だけじゃない
Claude Codeを使っていると、ついつい「もう少し賢くなれば、この問題も解決するのでは」と考えがちです。
もちろんモデルが改善することには大いに期待しています。でも、いくら賢くなっても、手続き上の制約、状態の保存、監査の履歴、ファイルのアクセス権限といった部分まで自然言語の指示で抱え込ませる必要はないと思っています。
人間の開発現場でも、どれだけ優秀なエンジニアがいても、GitやテストやCI/CDやレビューをなくしたりはしないわけで。
私がoneShotでやりたいのは、その当たり前をCoding Agentの開発にも持ち込むことです。
- 作ったものを無監査で通さない
- セッションが切れても現在の状態を失わない
- 実装が変わったら仕様との乖離に気づける
- 判断の根拠を後から追える
- ハーネスの改善自体も測定・検証する
どれも派手な仕掛けではありません。でも、この地味な仕組みを積み重ねる方が、プロンプトをどんどん長くするより、自分には合っているようです。
大昔から「オレがもう一人いればなぁ……」と思うことがありました。Coding Agentを使っていると、それが少し実現した感じもあります。
ただし、もう一人のオレがとんでもない勘違いをしても、そのまま本番に流れていかないようにしておかないとねw
そんなわけで、当面の投資先は引き続きハーネス層です。
48版まで来たけど、たぶんまだまだ変わりますwww
補足: この記事の機能説明は、手元の oneShot v0.48.0 初期テンプレート(CLAUDE.md、.claude/settings.json、各種Hooks・Skills・テスト)に基づいています。コードは説明用に一部簡略化しています。全機能のセキュリティ保証や品質改善の統計的実証を意味するものではありません。
