Claude Code の無人実行が止まる2つの型

無人で止まる要因は大きく2種類あり、直し方が違います。

  • 型A:許可で止まる。ツールの実行を聞かれた時点で、答える人がいないので拒否になる
  • 型B:コマンド定義が確認を求めて止まる。許可はすべて通っているのに、確認待ちに入って終わる

見分け方は単純です。拒否のログが出ていれば型A、拒否が出ていないのに途中で「〜しますか」と書いて終わっていれば型Bです。

型Aは許可の渡し方を直します。型Bは許可をいくら足しても直らず、プロンプトに前提を添えて直します。混ぜて対処すると、型Bに許可を足し続けることになります。

型A:許可で止まる(無人化の過程で分かった6点)

2026年9月5日〜6日に note の無人化を通す過程で、実測して分かった6点です。

1. 実行ファイルは絶対パスで指定する

launchd は PATH を継承しません。ターミナルで claude と打てば動くのは、シェルが PATH を持っているからです。ラッパーでは ~/.local/bin/claude のように絶対パスで呼びます。

2. 許可はスクリプト名の列挙では通らない

許可規則に実行したいスクリプトの名前を並べても通りませんでした。Bash(python3:*) や Bash(cd:*) のような、コマンド単位の粒度が要ります。

仮想環境(venv)の python は python3:* に一致しないため、別の規則として渡します。

3. 読めるのは --add-dir に入れた場所だけ

--add-dir に含めていないリポジトリは読めません。別リポジトリの台帳や正典を参照するジョブでは、起動時に読む先をすべて列挙します。

4. シェル構文は許可規則で解決できない

for f in *.md; do …; done のような構文は、Contains simple_expansion として構造的に拒否されます。許可をいくら足しても通りません。

直す場所はコマンド定義の側です。ループや展開を使う手順を、定義から消すしかありません。

5. .claude/settings.json の許可は当てにしない

settings.json に Bash(grep:*) を書いてあっても、無人実行では拒否されました。必要な道具は、起動時に --allowedTools へ全部渡します。ラッパーが「このジョブに必要な道具の一覧」を持つ形になります。

6. リダイレクト先は書き込み範囲に入れる

> /tmp/x.txt は、--add-dir の外への書き込みとして拒否されます。2026年9月6日には、トピック生成のコマンドが既存トピックの一覧を /tmp に出そうとして落ちました。

対処は、リポジトリ内に一時置き場を1つ作り、プロンプトで「一時ファイルはここに置く」と明示することです。

書き込みの境界は1行で決まる

書き込みを許す範囲は Edit(//<絶対パス>/**) の1行が作っています。先頭のスラッシュは2本です。

ここでツール名だけの Write に簡略化すると、ディスクのどこにでも書けるようになります。拒否を減らそうとして境界ごと外す変更は、いちばん危険です。範囲は絶対パスで書き、ツール名だけに落とさないようにしています。許可規則の考え方そのものは「AIエージェント権限設計」で扱っています。

型B:確認待ちで止まる

型Bの実例は2026年9月5日です。note の下書き作成コマンドが、見出し画像を3本作ったところで「下書きを今このまま作りますか」と聞いて終了しました。

許可は全部通っていました。コマンド定義に「件数と内訳を示してから実行する」と書いてあり、対話で使うなら正しい作法です。無人では答える人がいないので、そこで手番が終わります。

直し方は、プロンプトの末尾に前提を1段落添えることでした。同じ実行で、完走を再現確認しています。前提に入れている内容は次のとおりです。

  • launchd から起動された無人実行で、画面の前に人はいない
  • 途中で確認や承認を求めない。聞かれても答える人がいないので、そこで止まる
  • 検品と停止条件は外さない。基準に満たないものは実行せず、理由を名指しで報告する
  • ブラウザを使うなら headless で開き、使い終わったら閉じる
  • 一時ファイルは決めた置き場に置く
  • 最後に、実際に何をどこまでやったかを1行で書く

書き方には注意があります。「確認を省いてよい」と書くと、検品まで省かれるおそれがあります。「聞いても答えが返らない」という事実だけを伝え、検品と停止条件は残す、という書き分けにしています。

成否は申告でなく成果で判定する

2026年9月5日の実害です。note の2回目の実行は、拒否が13件出て「失敗」と報告されました。実際には、記事を2本書いて登録し終えていました。

拒否は、本体ではない補助的な手順でも起きます。拒否が出たことと、仕事が失敗したことは別の話です。

そこで呼び出し側は、成果で成否を判定します。コラムのジョブでは次の2つを置いています。

  • 公開済み記事ファイルの数を実行前後で数え、増えていなければ中止する
  • 公開後に、その URL が 200 を返すかを確かめる

もう1点、拒否の記録の取り方です。最初の実測では「Bash が13件」としか出ず、何を許せばよいのか分かりませんでした。ツール名だけでは直せないので、Bash なら実際のコマンド行、ファイル操作ならパスまで記録します。この記録があると、次回の許可の追加が1行で済みます。

暴走と同時起動を起動側で止める

往復の上限と時間の上限

2026年9月10日に、日次改善のジョブが177往復、読み直しだけで34Mトークンまで迷走しました。プロンプトで「迷ったら止まる」と書いても、迷走そのものは防げません。起動側の安全弁が要ります。

実装では出力を stream-json にして、1行ずつ読みます。assistant のメッセージを往復として数え、上限を超えたらプロセスを打ち切ります。時間の上限も同じ場所で見ます。まとめて読む方式だと、終わるまで制御が戻らず、途中で止められません。

打ち切ったときは「往復上限で打ち切り」と記録に残します。成果があったかどうかは、前の節のとおり別に判定します。

重いジョブは1本ずつにする

launchd は、Mac が寝ている間に来た回を、起きた瞬間にまとめて発火させます。relmea では昼間に note 3本とラジオ2本を置いているので、1日閉じたまま夜に開くと、5本が同時に立ち上がりえます。claude とブラウザが5つずつ動く形です。

Mac が起きている時間帯は実測しました。死活記録(heartbeat)19日分で、10〜18時はほぼ起きていて(19日中17〜19日)、0〜8時はまず動いていません。寝ている間に溜まって一斉に発火する状況は、現実に起こりえます。

対処は、ファイルロックで1本だけ走らせることです。先に走っているものがあれば、待たずに見送ります。日次のジョブなので、1日飛んでも翌日に取り戻せるためです。キューに並べて順に流す方式は、深夜まで走り続けるので採りませんでした。

見送ったときは、必ずログに残します。黙って終わると、止まったのか見送ったのかが後から分かりません。

スリープ明け・ブラウザ・公開前の機械ガード

スリープ明けはネットにつながっていない

launchd は、寝ていた間に過ぎた定時ジョブを、起きた直後に1回だけ走らせます。その瞬間は DNS がまだ引けず、最初の HTTPS で落ちて、その日の仕事が丸ごと消えます。

2026年9月22日と23日に、URL の書き戻しジョブが2日続けてこの形で落ちました。対処は、名前が引けるまで15秒おきに最大10分待ち、それでも引けなければ中止する、というものです。ジョブの先頭で呼ぶ、数十行の待ち合わせです。

長い実行がアイドルスリープで途切れないよう、claude の起動は caffeinate -i で包んでいます。

ブラウザのプロファイルは専用にする

同じブラウザのプロファイルは、2つのプロセスで開けません。無人ジョブでは専用のプロファイルだけを使い、起動前に誰かが開いていないかを検知します。2026年9月5日の実測でも、別のセッションに掴まれたままのプロファイルが残っていました。

公開前の機械ガード

Web サイトのデプロイが、フォルダの中身をそのまま上げる方式の場合、置きっぱなしの書きかけがあれば一緒に公開されます。

デプロイの前に、変更が「記事追加で当然変わる場所」だけかを確かめます。具体的には記事フォルダ、sitemap、リダイレクト設定です。想定外の変更が1件でもあれば、止めて名指しで報告します。公開の前後に置く検品全般は「AI自動投稿の安全対策」で扱っています。

実行環境の版が変わる問題は別に扱う

2026年9月18日の自動更新では、子エージェントが既定でバックグラウンド起動になり、無人の司令塔が子の結果を待たずに終わる事故も起きました。これは起動側の環境変数で同期実行に戻せます。経緯と確かめ方は「AIエージェントの無人実行と版の変化」に書いています。

起動側に持たせるもの(チェックリスト)

ここまでを、ラッパースクリプトの持ち物として並べ直します。

  • claude の絶対パスと、--add-dir・--allowedTools の完全な一覧
  • 書き込み範囲を Edit(//絶対パス/**) で固定し、一時置き場をプロンプトで明示する
  • 無人であることの前提段落(検品と停止条件は残す)
  • 往復と時間の上限、stream-json の逐次読み
  • 拒否は実際のコマンド行まで記録し、成否は記事ファイルの増減や URL の 200 で判定する
  • 重いジョブのファイルロックと、見送りのログ
  • スリープ明けの名前解決待ち、caffeinate -i、専用ブラウザプロファイル
  • デプロイ前の変更範囲チェック

多くは、実際に止まった箇所から1つずつ足したものです。最初から全部を作る必要はありません。止まったときに、ツール名ではなくコマンド行まで残る記録があれば、足すべき1行は見つかります。

この記録の前提と、分かっていないこと

  • macOS と launchd での実測です。他の OS やスケジューラでは、PATH やスリープ明けの挙動が異なります。
  • Claude Code の版によって挙動は変わります。許可の粒度や拒否の出方は版が上がると変わりうるので、手順は記載時点(2026年10月)の環境のものです。
  • 往復上限の値、名前解決待ちの10分、ロックの方式は、この環境で運用している設定です。最適値かどうかは検証していません。
  • Mac の起動時間帯は、死活記録19日分の観測です。長期の傾向は未確認です。