Claude CodeのMonitorツール、persistent: trueを指定しても30分で強制終了される件を追った
Claude CodeのMonitorツールでpersistent: trueやtimeout_ms: 3600000を指定しても、実際には約30分でセッションが失効する不具合をGitHub Issue #94553・#94393の一次情報から検証。原因はpersistentフラグの仕様変更とスキーマ上限の乖離、現状の回避策まで整理した。
エンジニアのゆとです。
Claude CodeのMonitorツールにpersistent: trueを渡して、外部プロセスの監視をセッションに任せっぱなしにしたことがある人は多いと思う。ログの末尾をtailし続けるとか、キューにメッセージが来るのを待つとか、終わりのタイミングが読めない監視作業をClaude自身にやらせておく、あの機能だ。
ところが最近のバージョンだと、これがどう頑張っても30分で切れる。persistent: trueを指定しても、timeout_msにスキーマ上の最大値である3600000(1時間)を渡しても、結果は同じ。ツールの返答には「expires in 30m unless the source ends first」と出て、30分きっかりで[Monitor expired after 30m ...]という通知が飛んでくる。セッションがアイドルだったわけでもなく、ずっと作業を続けていた最中でもこれが起きる。
GitHub Issueを見ると、この症状は複数の環境から独立して報告されていて、しかも原因の見え方が微妙に食い違っている。この記事ではanthropics/claude-codeのIssue #94553と#94393を一次情報として読み込み、何が起きているのか、公式の反応はどうなっているのか、現状取れる回避策は何かを整理した。
まず結論 — 何が起きているか
| 項目 | 内容 |
|---|---|
| 症状 | persistent: trueやtimeout_ms: 3600000を指定しても、実際の生存時間は約30分 |
| 発生時期 | 2.1.26x系(具体的には2.1.268前後)から。2.1.280でも継続報告あり |
| 原因 | persistentフラグが2.1.271前後で仕様変更され、無条件30分キャップに置き換わった。ただし変更告知が薄く、スキーマのmaximum表記も実態と食い違っている |
| バグか仕様か | Anthropic側のコメントで「intentional」とする情報はあるが、Issue自体は2件ともOPENのまま(2026年9月時点) |
| 影響 | 30分ごとの強制再アームでトークン消費が増える、再アームの隙間でイベントを取りこぼす、モデルが再アームを忘れて監視が止まる |
厄介なのは、persistentというパラメータ自体が受け付けられていて、明示的なエラーにもならない点だ。指定したつもりで安心していると、静かに無視されている。
症状: persistentもtimeout_msの最大値も効かない
Issue #94553の報告者は、Linux環境のClaude Code 2.1.272(2.1.268でも再現)で、メールポーリングのような長時間監視をMonitorに任せていた。
Monitor({command: "python3 watch.py --interval 180", description: "new mail", persistent: true, timeout_ms: 3600000})
ツールの返答は「expires in 30m」で、実際に30分後に[Monitor expired after 30m ...]という失効通知が届く。9月の変更前は同じセッションでpersistentが効いていたと報告されており、ドキュメント通りなら本来「セッションが終わるかTaskStopが呼ばれるまで動き続ける」はずのものだ。
Issue #94393はさらに踏み込んでいて、timeout_msに24時間(86400000)を渡すと、そもそもスキーマバリデーションで弾かれることを報告している。
InputValidationError: [
{
"origin": "number",
"code": "too_big",
"maximum": 3600000,
"path": ["timeout_ms"],
"message": "timeout_ms must be <= 3600000"
}
]
スキーマ上は1時間(3600000ms)まで許可されているように見えるのに、その最大値を指定しても返ってくるのは「expires in 30m」。つまりスキーマのmaximum表示と、実際にツールが守る生存時間が最初から一致していない。
原因1: persistentフラグは「仕様変更」として置き換えられていた
Issue #94553のコメントで、ruihe774氏が2.1.271のチェンジログを引用している。
Changed Monitor watches to always have a deadline (at most 30 minutes; 10 in single-prompt
-pruns) and notify Claude to re-arm, replacing the no-timeoutpersistentoption
つまりpersistentの無期限モードは、ある時点で「常に30分(単発の-p実行では10分)の上限を持たせて再アームを促す」仕様に意図的に置き換えられていた、ということになる。バグではなく仕様変更だったという情報だ。
問題は、この変更が入力スキーマに反映されていないこと。hulm1818gm氏が2.1.272のsdk-tools.d.tsを確認した報告によると、MonitorInputのtimeout_msのドキュメントコメントには「Deadlines above 1800000ms are capped to 1800000ms(180万ms=30分を超える指定は30分に丸められる)」と正しく書かれている一方、バリデーションのmaximumは3600000(1時間)のまま残っている。さらにpersistentはMonitorInput側には存在せず、MonitorOutput側(出力の型定義)にだけ「No timeout — runs until TaskStop or session end」という説明付きで残っている。要するに、受け取る側の型定義からは削除されたのに、返す側の型定義にだけ痕跡が残っている状態だ。ドキュメントとスキーマと実装がそれぞれ別々のタイミングで更新された結果、こういうズレが起きたように見える。
原因2: スキーマの上限表記と実際の生存時間が食い違ったまま
kepler-somnium氏は6台構成のマルチシート環境(Linux/macOS、2.1.267〜2.1.276)で計測結果を報告している。2.1.267ではMonitorツールのスキーマにpersistentプロパティがまだ存在し、実際に6時間以上動き続けた監視があった一方、2.1.273・2.1.276ではpersistentプロパティ自体がスキーマから消えていて、渡してもエラーにならず単に無視されるだけだったという。timeout_ms: 60000(1分)でpersistent: trueを付けて実行しても、結果は「1分」ではなく規定の30分キャップで動いていたとも報告されている。
persistentが消えたバージョン(2.1.273以降)と残っていたバージョン(2.1.267)の間で挙動が変わったのは間違いなさそうだが、どのパッチで何が変わったかの正式なアナウンスはIssue上では見当たらない。
実害: 再アーム地獄でトークンを消費し、イベントも取りこぼす
この仕様変更が地味に痛いのは、「30分ごとに再アームすればいいだけ」では済まない実害がいくつか報告されている点だ。
kepler-somnium氏の計測では、3本の監視(メール・キュー・Webhook相当)を同時に張っていたシートで、5時間のうちに33回の再アームが発生していた。しかも別の4シート構成では、55分間隔で自分でスケジュールしていたはずのハートビート処理が、Monitorの失効通知に毎回先を越されて一度も発火しなかったという。実効的なウェイクアップ間隔が、自分で設計した間隔ではなく強制的に30分キャップに支配されてしまう格好だ。
travish-appfolio氏が指摘しているのはイベントの取りこぼしだ。外部プロセスがログファイルに追記するのをtailで監視するような用途では、失効から再アームまでの隙間に書き込まれた行は、二度と拾えないまま消える。ログの監視主体がMonitor自身ではなく別プロセスである以上、「タイミングを合わせて再アームする」ことはそもそもできない。
benpetermorris氏はさらに一段深い問題を報告していて、失効通知が来てもモデルが確実に再アームするとは限らないという。作業の途中で失効通知を受け取っても、モデルが「今はもっと優先度の高い作業をしている」と判断して再アームを後回しにするケースがあり、結果として監視自体が止まったままになる。モデルの注意力に再アームの成否を委ねている以上、これは仕組みとして脆いという指摘だ。

現状の回避策
根本的な解決策(persistentの復活や設定可能な上限)はまだ実装されていない。現時点でIssue上に出ている回避策は次の3つだ。
1. プロセス自身に終了させて run_in_background で拾う
hulm1818gm氏が提示している方法で、監視対象のスクリプト側に「最初のイベントを受け取ったら自分で終了する」処理を書き、それをMonitorではなくrun_in_background付きのBashで起動する。プロセスが終了するとセッション側に通知が届く仕組みを利用するもので、run_in_backgroundのバックグラウンドプロセスはMonitorの生存時間キャップの対象外になる。ただし1回のイベントで終わるので、ストリーミングで複数イベントを拾い続ける用途には向かず、イベントのたびに再度起動し直す必要がある。
2. プラグインの monitors/monitors.json を使う
hassaans氏が2.1.280で検証した内容によると、プラグインのマニフェストで定義するmonitors/monitors.json経由の監視は、公式ドキュメント通り「セッションの間ずっと動き続ける」挙動のままで、30分キャップの対象になっていなかった。同じ「2400秒スリープしてから出力する」コマンドを、Monitorツール経由と--plugin-dirで読み込んだプラグイン監視の両方で試したところ、Monitorツールは30分で失効した一方、プラグイン監視は40分経過時点まで生存して正常終了していたという。
ただし制約がある。プラグイン監視はインタラクティブセッション限定(-pの単発実行では動かない)で、コマンドはマニフェストに固定で書く必要があり、モデルが動的に監視対象を決めたり後から変更したりはできない。「常時同じコマンドを監視し続けたい」場合の代替にはなるが、「状況に応じて監視対象を変える」用途の代わりにはならない。

3. 30分キャップを前提に運用を組み直す
回避策というより諦めに近いが、jclerc氏やgtapps氏のように「再アーム自体は無駄ではないが、コストと信頼性を承知の上で運用する」という声もある。長時間監視が必須の作業では、失効通知を見落とさないようにタスクの優先度を明示的に上げておく、再アームの成否を別途ログで確認するなど、モデルの気まぐれに頼らない仕組みを自分側で足すしかない。

公式の反応と今後の見通し
Issue #94553・#94393はいずれもbug・has repro・area:toolsラベルが付いた状態で、2026年9月24日時点でどちらもOPENのままになっている。ruihe774氏が引用したチェンジログの記述からは「意図した仕様変更」であることがうかがえる一方、Issue自体がクローズされていないということは、Anthropic側でも「意図通りだが今の実装のままでは実用上問題がある」という位置づけで扱われている可能性がある。
raldebsi氏は「古いバージョンにダウングレードしたら制限が完全にサーバー側にかかっていることが確認できた」とコメントしていて、クライアント側のバージョンを戻しても回避できない(=クラウド側で制御されている)ことも報告されている。つまりローカルの設定やクライアントのダウングレードでは根本的な解決にならない。
要望として複数の報告者が共通して挙げているのは、「上限を撤廃してほしい」ではなく「上限を設定可能にしてほしい(環境変数か設定ファイルで)」という現実的なラインだ。同じ2.1.268のリリースで追加されたWebFetchのデッドラインにはCLAUDE_CODE_WEBFETCH_DEADLINE_MS=0というオプトアウト用の環境変数が用意されているのに対し、Monitorのpersistent廃止には同等のオプトアウト手段が用意されていない、という非対称性を指摘する声(travish-appfolio氏)もある。
FAQ
persistent: trueはもう完全に廃止されたの?
入力パラメータとしては受け付けられるが、2.1.271前後以降は効果を持たない。エラーにもならず、指定しても無視されるだけなので、コード上でpersistent: trueを書いていても実際の挙動は変わらない。
timeout_msを短めに指定すれば安定する?
30分より短い値(たとえば600000=10分)を指定した場合の挙動はIssue内でも報告がまちまちで、確実に短い時間で切れるとは限らない。少なくとも30分より長く生存させることはできない、という点は複数の報告で一致している。
「30分ごとに再アームする」運用で本当に問題ない?
トークン消費が増える(kepler-somnium氏の実測で5時間33回)ことと、再アームの隙間でイベントを取りこぼす可能性がある点は許容した上での運用になる。ログのようにイベントを1件も逃したくない用途では、再アーム方式そのものが構造的に不向きだ。
この記事の情報はいつの時点のもの?
Issue #94553は2026年9月15日作成・9月22日のコメントまで、Issue #94393は2026年9月14日作成・9月22日のコメントまでを確認している。この種の挙動はマイナーアップデートで頻繁に変わるため、実際に使う際はclaude --versionで自分の手元のバージョンを確認し、両Issueの最新コメントも合わせて見ておくのが確実だ。
外部の非同期イベントを監視する設計自体は便利だが、今のところ「30分ごとに起こされる」ことを前提にした設計にしておくのが現実的だ。プラグイン監視という抜け道はあるものの、動的に監視対象を変えたい用途には使えないので、当面はコストと取りこぼしのリスクを飲み込んだ上で使うしかなさそうだ。