← 一覧に戻る

rate-limit自動復帰 — 調査・修正レポート

2026年4月2日 20:10 更新
MD から自動変換されたページです。内容について質問があれば右下の ? ボタンからどうぞ。

調査日: 2026-04-02 対象: ~/.claude/scripts/rate-limit-recovery.sh(StopFailure hook)

概要

rate-limitに引っかかった作業セッションが自動復帰できていない問題を調査・修正した。 4つの根本原因を特定し、すべて修正済み。


根本原因(4つ)

原因1: at デーモンが動いてない(最大の原因)

項目 内容
症状 リカバリスクリプトが予定時刻に実行されない
メカニズム at コマンド自体は成功しジョブ登録される → atrun(実行デーモン)が停止中で永遠に実行されない
影響 nohup+sleepフォールバックは at がエラーの時だけ発動 → at が成功扱いなのでフォールバックされない
証拠 atq に 04/01 22:15 のジョブが04/02 12:51時点でまだ残存

修正: at を完全廃止。常に nohup+sleep を使用。

設計判断 — なぜ launchd ではなく nohup+sleep:

原因2: reset時刻のパース失敗

項目 内容
症状 実際は45分〜1時間で済むのに5時間待ちにフォールバック
メカニズム last_assistant_message"You've hit your limit · resets 11pm (Asia/Tokyo)" が入っているが、スクリプトはこの値を読んでいなかった
旧動作 capture-pane の Xh(@XXam/pm) パターンのみ → マッチしない → デフォルト5h
証拠 ログ: Default 5h. Recovery at 03:15(実際のresetは11pm = 23:00、わずか45分後)

修正: 復帰時刻の抽出を多段フォールバック構成に変更。構造化データ(resets_at)を最優先とし、自由文パースは2番手に降格。日付跨ぎ判定(算出時刻が過去なら翌日)も追加。

原因3: stale pending flag

項目 内容
症状 新しいrate-limitが検知されても「already pending」で弾かれる
メカニズム 原因1で実行されなかったリカバリのpending flagが残存 → 同セッションの新規リカバリをブロック
証拠 04/02 09:55: Recovery already pending for main (pending_main_1775049336_75582) — 11時間前のフラグ

修正: pending flagを拡張。PID・予定時刻・セッションID・execスクリプトパスをメタデータとして保存。有効判定は「PID生存 AND 予定時刻が未来」の場合のみ — それ以外(PID死亡 OR 予定時刻過去)は全てstaleとして自動クリーンアップ。

原因4: race condition(重複hook発火)

項目 内容
症状 同時に2つのリカバリスクリプトが生成される
メカニズム サブエージェントのStopFailure(agent_id付き)も親と同時に発火
証拠 04/01 22:15:36 に2つのhook: keys差分に agent_id, agent_type の有無

修正:


修正ファイル

ファイル 変更内容
~/.claude/scripts/rate-limit-recovery.sh メインhookスクリプト(全面改修)
~/.claude/scripts/rate-limit-recovery-template.sh リカバリ実行テンプレート(新規)
~/.claude/scripts/test-rate-limit-recovery.sh テストスイート(新規、21テスト)

アーキテクチャ変更

旧: メインスクリプト内でヒアドキュメントでリカバリスクリプトを生成(変数展開が複雑) 新: テンプレートファイル + BSD sed 置換でリカバリスクリプトを生成(保守性向上)

復帰時刻の抽出優先順位

  1. 4a: JSON resets_at フィールド(構造化データ — 最も信頼性が高い)
  2. 4b: last_assistant_messageresets XXam/pm パース(自由文、表記揺れリスクあり)
  3. 4c: capture-pane の Xh(@XXam/pm) ステータスバー表示
  4. 4d: デフォルト5時間

※ 4b/4cでは日付跨ぎ判定あり(算出時刻が過去なら翌日と判断)


クリーンアップ実施


テスト結果

reset時刻パース

PASS: 'resets 11pm (Asia/Tokyo)' → 23:00
PASS: 'resets 11am (Asia/Tokyo)' → 11:00
PASS: 'resets 3am (Asia/Tokyo)' → 03:00
PASS: 'resets 12am (Asia/Tokyo)' → 00:00
PASS: 'resets 12pm (Asia/Tokyo)' → 12:00

優先順位テスト

PASS: resets_at + last_msg → resets_at used first
PASS: no resets_at → last_msg fallback
PASS: capture-pane pattern extraction works ('3h(@11pm)')
PASS: no resets_at + no last_msg + no capture-pane → default

race condition テスト(セッション固定名 mkdir 排他)

PASS: first mkdir succeeds (lock acquired)
PASS: second mkdir blocked (race condition prevented)

stale flag テスト

PASS: PID生存 + 予定時刻が未来 → 既存リカバリ有効、新規をブロック
PASS: PID死亡 + 予定時刻が過去 → stale判定、クリーンアップ後に新規実行
PASS: PID死亡 + 予定時刻が未来 → stale判定(プロセスが異常終了した場合)

テンプレート置換

残存プレースホルダー: 0
生成スクリプトシンタックス: OK

bash -n(構文チェック)

Main script: OK
Template: OK

macOS互換性

PASS: /bin/bash (3.2) での構文チェック通過
PASS: BSD sed デリミタ | での置換正常動作
PASS: date -j -f (BSD date) でのパース正常動作
PASS: stat -f %m (BSD stat) でのタイムスタンプ取得正常動作

実地検証方法

次回rate-limitが発生した時に以下を確認:

  1. ログ確認: tail -f ~/.claude/logs/rate-limit-recovery.log

    • Reset from JSON resets_at: または Reset from last_assistant_message: が出ること(4a/4bが機能してる)
    • Scheduled via nohup+sleep が出ること(atではなくnohup)
    • Ignoring subagent StopFailure が出ること(race condition対策)
  2. プロセス確認: cat ~/.claude/rate-limit-recovery/pending_*/pid でPIDを確認し、ps -p <PID> -o pid,ppid,etime,command= で生存確認

    • sleepプロセスが1つだけ存在すること
  3. 復帰時刻: 通知の復帰予定時刻が妥当か(5h固定ではなく、actual resetに近い値)

  4. 自動復帰: 予定時刻にntfy通知が来るか + セッションが再開されるか


無効化・ロールバック手順

自動復帰の緊急停止

# 1. 待機中のsleepプロセスを停止
for f in ~/.claude/rate-limit-recovery/pending_*/pid; do
    pid=$(cat "$f" 2>/dev/null) && kill "$pid" 2>/dev/null
done

# 2. pending flagとexecスクリプトを全削除
rm -rf ~/.claude/rate-limit-recovery/pending_* ~/.claude/rate-limit-recovery/exec_*

hook自体の無効化

settings.json の hooks.StopFailure からこのスクリプトのエントリを削除し、セッションを再起動。

通知だけ失敗した場合

ntfyの送信失敗はリカバリ処理をブロックしない(&>/dev/null & でバックグラウンド実行)。通知未着でもリカバリは実行される。ログ(~/.claude/logs/rate-limit-recovery.log)で確認可能。


残課題


Codexレビュー履歴

Round 結果 指摘数 主な指摘
R1 NG MAJOR×5, MINOR×3 launchd不採用の根拠不足、優先順位逆転、stale判定が時間固定、BSD sed互換未記載、テスト不足、race condition冪等性、ps grep雑、ロールバック手順なし
R2 NG CRITICAL×1, WARNING×2 mkdir排他がセッション固定名ではなく毎回一意(race condition未解決)、テストファイル不在、旧コメント残存
R3 NG WARNING×1, INFO×1 ntfy通知がロック取得前(二重通知リスク)、stale判定のレポート表現不一致
R4 NG WARNING×1 レポートにcapture-paneテストPASSと記載するも実テスト不在
R5 LGTM なし 全指摘解消。実装・テスト・レポートの三点が整合
📝 質問モード — テキストを選択してね
✓ 質問を送信しました