Claude Code の make-diary を朝の実行ログで振り返り、同期の完了待ちと実行モデルを手順書に固定した

開発claude-code-tools

朝の /make-diary は68分かかった。 そのうち11分は、Step 1 のログ同期をバックグラウンドに回したままターンが閉じ、何も進んでいなかった。

「続きをやってください」と打つと、エージェントはファイル一覧を確かめ、18ファイルで同期完了と書いて先へ進んだ。 同期はまだ終わっていなかった。 最終的な出力は19ファイルで、1セッション分が日記から漏れた。

完了メッセージを見ないまま、エージェントはなぜ先へ進めたのか。

朝の実行ログを Claude Code に読ませ、詰まった箇所を非公開の振り返り記事にまとめさせた。

同期スクリプトの走査範囲

振り返りで挙がった原因の一つは、同期そのものの遅さだった。 過去の日付を渡すと、同期スクリプトはセッション履歴の jsonl を全部(821本、5.4GB)走査し、12分かかる。

更新日時で絞る案は、報告を読んでそのまま採った。 対象日の 00:00 以降に更新されたファイルだけを読むよう、同期スクリプトを直させた。 実測では34本を33秒で読み、出力は絞る前と同じ19本だった。 環境変数 SYNC_FULL_SCAN=1 を付ければ、従来の全走査に戻せる。

実装では、細かい点が二つあった。

  • find -newer は「厳密に新しい」ファイルしか拾わないので、閾値を対象日 00:00:00 の1秒前に置いて境界を含める
  • 比較用の参照ファイルは実行ごとに別名にして、並行実行で互いに書き換えないようにする

touch -t-newer の組み合わせは、GNU と BSD のどちらでも同じに動く。

Step 1 の完了判定

エージェントが一覧だけで先へ進めた理由は、手順書の Step 1 にあった。 Step 1 には「ls でファイルが見えれば進んでよい」と書いてあった。 同期が終わる1分前に取った一覧でも、ファイルは見える。 エージェントは、手順書が許した条件どおりに進んでいた。

この逃げ道は削るべきだと判断した。 いまの Step 1 は、同期を前面で実行し(タイムアウトは Bash ツールの上限と同じ10分)、Processed N session files=== Output Files === の2行が出るまでログを読み始めない。

漏れていた1セッションは、前日の日記に補記させた。

作業ごとの実行モデル

ここで一つ白状した。 朝の実行では、オートモードをオンにできなかったので、途中に自分の手でモデルを Sonnet 5 に切り替えていた。

そのうえで、作業ごとにどのモデルを使えばよいのか、判断の基準を聞いた。 サブエージェントに投げるときなどは、手順書に明示してあったほうがいい気がしたからだ。 直す内容は、基本的に報告の指摘どおりでよいと伝えた。

make-diary の手順書の冒頭には、役割ごとのモデルを表で書かせた。

役割モデル
メイン(コマンドを回すセッション)Sonnet 5
Step 4 の記事サブエージェントOpus 5
Step 11〜11.9 の統計チェーンを委譲するサブエージェントSonnet 5
Haiku 4.5使わない

メインの仕事は、手順書どおりにスクリプトを叩いて結果を読む作業が大半だ。 記事の質はサブエージェント側で決まるので、Opus 5 はそちらに充てる。 Haiku 4.5 の行には、Step 1 をバックグラウンドに回してターンを閉じ、11分放置された件を理由として書いてある。 サブエージェントを起動するときは model を必ず明示する、という一文も足した。 未指定だと、親のモデルを継承するためだ。

サブエージェントを減らすか、外へ出すか

ルールが全部載ってしまう点や、手順書が直列を指示している点が報告に挙がっていて、それを読んでいるうちに別の案を思いついた。 コンテキストを小さくしたいなら、いっそサブエージェントをやめて、メインのエージェントだけで直列に全部回すほうがいいのではないか。 時間は多少伸びるかもしれないが、そのほうが確実だろうと考えた。

聞いてみると、メインで直列に回してもコンテキストは減らない、という試算が返ってきた。 振り返りでは、メインのコンテキストが10.4万トークンから49.1万トークンまで膨らんだ経過を、区間ごとの内訳にして出させている。 意味がないなら、直列化はやらなくていい。

ただ、報告にはほかにも判断を求められている項目があり、その中身が読み取れなかった。 Step 11 の扱いや、Step 9〜12 を references へ移す案が並んでいても、それぞれ何を決めるのかがわからない。 自分が何を聞かれているのかを聞き直した。

そのうちの一つが、Step 11〜11.9 の統計チェーンを誰がやるか、だった。 この統計チェーンは、メインで実行すると、変更がない日でもコンテキストが5.8万トークン増えていた。 案Aは、ここだけを general-purpose のサブエージェント1本に出す。

説明を受けて、次のように決めた。

  • Step 11〜11.9 は、案Aでサブエージェント1本に移す
  • Step 9〜12 を references へ移す案は、やらないほうがよさそうなので見送る

委譲先への指示文は、references に別ファイルとして置かせた。 委譲先は手順書の該当節を読み、「コミットしない」「単一ソースの異常値は保留する」「記事は作らない」を守る。 記事が要る日は委譲先に「記事が必要」と報告させ、本文はメイン側が Opus のサブエージェントで書く。

毎朝の手作業になっていた箇所

振り返りでは、朝のチェーンを手で埋めていた箇所も見つかった。

  • 統計の取得スクリプト5本は、生成時刻だけが変わった差分を毎朝出し、git checkout や Edit で戻す手間が発生していた。本文が同じなら書き込まない処理を足し、3本を再実行して git diff が空になることを確かめた
  • 株価の取得では、主ソースが月曜の終値を火曜の朝まで反映しておらず、4分待ったうえで JSON を手で書き換えていた。主ソースに対象日のデータが無ければ Yahoo からも取り、日付で併合する形にした
  • 決算チェックの手順では、銘柄一覧を展開する部分が毎朝の即興になっていて、パスの渡し方で3回失敗していた。そのまま貼れる確定コマンドを置いた

Yahoo からのデータは、置き換えではなく併合にした。 Yahoo は約1か月分しか返さないので、置き換えると、52週レンジの材料になる約1年分のデータが消える。 これは、Codex のコミットレビューで指摘されて直した点だ。

締めゲートは何を止めていたのか

コミットが済んだあと、締めゲート(carryover)が何をしているのかを聞いた。 8月11日に入れた仕組みだが、中身をもう忘れていた。

説明は、「計画書の進捗を、コミットのたびに現実に追いつかせる仕組み」だった。 毎朝「積み残しありますか」と口頭で聞いていたのを自動化しようとしたとき、memo の計画書には未チェックの項目が148ファイルに1,669件あり、読んで判定できなかった。 そこで、計画書ごとに状態を機械で読める形で持たせた。 締めゲートは、targets に書いたパスへの変更がステージされたとき、状態の確認日が今日でなければコミットを止める。

この日だけで、.claude/skills/.claude/references/ を丸ごと宣言した計画書2本に7回引っかかり、そのたびに理由を書いてゲートを飛ばしていた。

2本の targets を、実際に触ったファイルへ絞ることにした。

  • スキル棚卸しの計画書:.claude/skills/.agents/skills/ から、計画書自身と、その計画で作ったスクリプト2本へ
  • コンテキスト肥大の棚卸し計画書:.claude/references/ と CLAUDE.md などの指示ファイルから、計画書自身と締めゲートの規約ファイルへ

あわせて、この仕組み自体は機能している、という理解で合っているのかも確かめた。 直したのは計画書2本の宣言の1行ずつで、ゲート本体には手を入れていない。 規約にある「置き場全体を書かない」に、宣言のほうを合わせた。

ステータスラインのリセット表示

別のセッションでは、ステータスラインにも手を入れた。 レート制限の欄は、7d 91% (2d15h) のようにリセットまでの残り時間を出していた。 2d15h を見て、リセットが金曜なのかをいちいち自分で確かめることがあった。

1日以上先のリセットは、実際の日時を曜日付きで出すように変えさせた。 5時間枠は、相対表示のままでいいことにした。 変更したのは statusline.pytime_until() で、5時間枠は1日を超えないので、この変更の影響を受けない。

動作確認では、日本語の曜日が化けた。 文字コードの都合によるもので、UTF-8 の出力を強制して確かめ直すと正しく出ていた。

反映後のステータスラインを貼ると、7日枠は 9/18(金)00:00 になっていた。 ただ、2行目の末尾にある Fable 枠は (9/18(金... で切れていた。 2行目がレート制限で埋まっていたので、dev サーバーやプロセス数の表示を1行目のプロジェクト名とモデル名の並びへ移し、2行目はコンテキストとレート制限だけにした。

コミットを頼むと、別作業とみられる変更がステージに混ざっていた、と報告があった。 そちらはステージから外し、statusline.py だけでコミットしている。 Codex のコミットレビューは、critical と high の指摘なしで通った。

サブエージェントの既定モデル

昼前に、ユーザー設定の settings.json の envCLAUDE_CODE_SUBAGENT_MODEL: "opus" を足させた。 サブエージェントの既定モデルを opus にするための環境変数で、追加したあと JSON として壊れていないことも確かめさせた。

今日の学び

  • 1セッションが日記から漏れた原因は、手順書に書いてあった「ls でファイルが見えれば進んでよい」だった。エージェントは許された条件どおりに進んでいたので、直す先は手順書だった
  • コンテキストを減らしたくてサブエージェントをやめる案を出したが、試算で減らないとわかった。実際に採ったのは逆向きで、統計チェーンをサブエージェント1本に切り出した
  • 締めゲートが無関係なコミットを1日に7回止めていたのは、置き場を丸ごと宣言した計画書2本のためだった。直したのは宣言のほうで、ゲート本体には手を入れていない
  • 2d15h のような残り時間を毎回日付に換算していたので、日時の計算はステータスライン側に任せることにした
#Claude Code#make-diary#サブエージェント#締めゲート#ステータスライン#開発日記