Claude Code 自走開発シリーズ 第4弾
← 前の記事:Claude CodeでWeb・Server・Clientを並行開発する|複数担当を自走させる設計
Server、Web、Client、Integrationへ担当を分け、Taskも用意しました。
各担当が自分で次のTaskを拾える。
あとは定期実行すれば、人が毎回起動しなくても勝手に進む。
自分も最初はそう考えていました。
実際に動かすと、
定期実行できることと、自走できることは別でした。
この記事では、自分の環境で使っていたClaude Code Desktopの**ローカル定期実行(Desktop scheduled task)**で、実際に詰まったところをまとめます。
以下では単に「ローカル定期実行」と呼びます。
この記事で扱うのは、自分のClaude Code Desktop環境で使っていたローカル定期実行の事例です。
Anthropicが現在公開しているClaude Codeの「Routines」はクラウド上で実行され、PCを閉じていても動く別の仕組みです。
ここで書く権限、発火時刻、UI表示などは自分のローカル環境で観測したもので、Claude Code全体の固定仕様ではありません。
最初に作った形
4つのローカル定期実行を別々に動かします。
最初は同時起動を避けるため、実行時刻も分けました。
狙いは単純です。
担当Aが作業
↓
少し後にIntegrationが確認
↓
別の時間帯で担当B
↓
別の時間帯で担当C
ところが、動かし始めると別の問題が出ます。
問題1:自動起動したのに権限確認で止まった
最初に起きたのがこれです。
定期実行自体は発火しました。
セッションも起動しました。
しかし、最初にPowerShellを使おうとしたところで承認待ちになりました。
これでは「自動で起動する仕組み」です。
自走ではありません。
自分の中で、自動起動と自走は別物だとはっきり分かったのがこの停止でした。
人がいない時間に動かすなら、
起動できるか
ではなく、
起動後の必要操作を人なしで通せるか
を見る必要があります。
問題2:全許可にすれば終わり、でもない
では、承認をなくせばよいか。
それも単純ではありません。
権限を広げると、自走用の指示に含まれている
ファイル編集
build
test
Git操作
commit
push
なども、承認待ちなしで実行できる範囲が増えます。
自走性は上がります。
同時に、誤った判断がそのまま実行へ進む範囲も広がります。
自分は権限だけを広げるのではなく、
対象Repositoryを限定
担当branchを限定
mainへの直接変更禁止
force push禁止
破壊的操作禁止
担当外変更禁止
不明差分があれば停止
をセットにしました。
ただし、プロンプトに「禁止」と書くだけでは強制的な保護にはなりません。
mainや本番環境など本当に守りたい場所は、branch protection、認証情報の分離、OS権限、CI側のGateなど、AIの指示とは別の仕組みでも保護します。
「権限を広げる」と「技術的なガードレールを置く」はセットです。
問題3:設定時刻と実起動に差があった
自分の環境では、設定したcron時刻に対して実際のdispatchが数分遅れることがありました。
ログ上では負荷分散用の遅延として確認できました。
このとき困るのが、
12:00 Server開始
12:02 Server終了予定
12:03 Integration開始
のような設計です。
Server側の処理時間も毎回同じではありません。
スケジューラ側の遅延もあります。
数分単位で前工程の完了を期待すると崩れます。
現在は、
スケジュールを依存関係の保証には使わない
ようにしています。
Integration側は時刻ではなく、
対象TaskがDONEか
対象branchが存在するか
ロックの状態と所有者が妥当か
既存Session / processが残っていないか
必要な成果物があるか
を見て判断します。
後で実際に事故寸前まで行ったため、現在は「ロックが空いている」だけでは開始条件にしていません。
cronは「仕事があるか見に来る時刻」として使います。
Task A → Task Bの順序そのものは、時刻ではなくDONE / READY / BLOCKEDなどの状態と依存関係で決めます。
問題4:UI表示だけでは設定を判断できなかった
複数時刻を登録したとき、自分の環境では一覧の要約表示だけを見ると、先頭側の時刻しか設定されていないように見えることがありました。
そのため、
UIの表示
保存された設定
実際の発火ログ
次回実行予定
を分け、一覧表示だけで設定状態を判断しないようにしました。
問題5:ローカル設定1つでworking treeがdirtyになる
自走の開始条件に、
working treeがclean
を入れていました。
その状態でローカル設定ファイルを追加すると、.gitignoreなどで除外していなければ未追跡ファイルとしてgit statusに出ます。
?? local-settings.json
すると、AI側は、
所有者不明の差分がある
↓
安全のため停止
と判断します。
自走用の設定を追加した結果、自走が止まるわけです。
ローカル設定、ログ、ロック、証跡など、
自動化のために増えるファイルをGit上でどう扱うか
も決める必要がありました。
問題6:Repositoryルールと自走ルールが矛盾した
既存の開発ルールに、
自動commit禁止
自動push禁止
人の明示指示が必要
と書いてありました。
一方、自走用の定期実行側には、
Task完了後にcommit
feature branchへpush
と書いていました。
AIから見ると、どちらも指示です。
「今回は自走だから例外」と人間なら考えられますが、ルール上は矛盾しています。
そこで適用範囲を分けました。
通常の対話セッション:
commit / pushは人の指示が必要
自走用の定期実行:
指定feature branchへのcommit / pushのみ許可
mainへの直接commit / pushは禁止
force pushは禁止
自走させるほど、曖昧な運用ルールが表に出ます。
問題7:起動時にGitが想定外でも定期実行は来る
定期実行は、人がRepositoryを整えて待っているときだけ来るわけではありません。
例えば、
別セッションが作業中
未commit差分がある
merge途中
mainが大きく進んだ
同じTaskがRUNNING
想定外branch
の状態でも時刻になれば起動します。
そこで、Taskを始める前に必ず状態確認を入れます。
現在branch
HEAD
originとの差
working tree
Task状態
Task owner
対象worktree
ロック
異常なら、実装へ入らず停止します。
前回Sessionの終了報告は参考情報です。
次回起動時は、HEAD、origin、working tree、Task状態、lockなどを必ず再実測します。
引き継ぎ情報より、現在のRepositoryとGitの状態を優先する運用です。
自走は「止まらない仕組み」ではなかった
最初は、できるだけ停止しないことが理想だと思っていました。
今は逆です。
正常な状態なら人を呼ばず進む。
危ない状態だけ止める。
長時間動かすなら、この方が安心できます。
この7つを最初から想定して設計していたわけではありません。
実際に定期実行を回し、止まるたびに原因を調べて、次の起動では同じ理由で止まらないようにルールを足していきました。
まとめ
定期実行を登録しただけでは、自走にはなりませんでした。
実際に詰まったのは、
1. 権限確認
2. 権限を広げたときの安全性
3. スケジュール時刻と実起動の差
4. UI表示と実設定の差
5. ローカル設定によるdirty
6. 既存ルールとの矛盾
7. GitやTask状態が想定外のまま起動する
という部分です。
ここを直して、自動起動から自走へ近づけました。
ところが次は、もっと危ない問題が出ました。
動いているセッションのロックを、自己修復が「死んだ」と誤判定して解放しました。
次の定期実行まで数分。
そのままだと、同じworktreeへ2つ目のセッションが入る状態でした。
次の記事では、この事故寸前の事例から、heartbeatとロックをどう考え直したかを書きます。