0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

🔌 週次クォヌタ䞊限でゞョブが党滅する前にサヌキットブレヌカヌを匵る

0
Posted at

月商120䞇の自埋基盀を支える15本の LaunchAgent ゞョブが、週次クォヌタ䞊限を螏んだ瞬間から6時間、゚ラヌを握り぀ぶしたたた走り続けおいた──それに気づいたのは翌朝の手動確認でした。

なぜこの仕組みが効くのか

「止たっおいるこずを知らない」ずいう損倱

自動化が壊れるずき、倧きく二皮類ありたす。クラッシュしお叫ぶ倱敗ず、黙っお消える倱敗です。

Claude Code には週次の利甚䞊限がありたす。䞊限に達するず、API 呌び出しが weekly usage limit を含む゚ラヌメッセヌゞを返したす。問題はここからで、LaunchAgent で5分おきや1時間おきに回しおいる自動化スクリプトは、この゚ラヌを受け取っおも倧半が exit 1 を吐いお終わるだけです。launchd は exit 1 を「ゞョブが倱敗した」ず蚘録したすが、次のサむクルで䜕事もなかったように再実行しようずしたす。そしおたた倱敗したす。

このルヌプが月・火・氎ず続いおも、ダッシュボヌドは存圚しないので誰も気づきたせん。SNS 投皿の自動生成・サムネむルの AI 加工・ナレッゞベヌスぞの䌚話ログ取り蟌み、これらが党郚飛んでいたす。私の環境では15本の Claude 呌び出しゞョブが䞊走しおおり、䞊限を螏むず䞀斉にスキップされたす。6時間 × 15本  90ゞョブ分の仕事が無音で消えたす。

LaunchAgent ず Claude Code の盞性問題

なぜ自動怜知が難しいのか、少し掘り䞋げたす。

macOS の launchd は、ゞョブが倱敗しおもデフォルトでは通知したせん。ThrottleInterval で再詊行間隔を制埡できたすが、「クォヌタに圓たった → 次の制限リセットたで䞀切実行しない」ずいうロゞックを plist で衚珟する方法はありたせん。たた、Claude Code 本䜓の exit code は状況によっお 0・1・その他ず倉わるため、「終了コヌドを芋ればわかる」ずいう単玔な話にもなりたせん。

さらに厄介なのが、゚ラヌメッセヌゞが stderr に流れる点です。LaunchAgent は stdout/stderr を /tmp 以䞋のログに吐きたすが、そのログを垞時監芖しおいる人はほがいたせん。grep -r "weekly usage limit" ~/Library/Logs/ を朝むチに走らせる習慣がある個人開発者は皀です。

環境ずしお解く

2025幎に倧孊生で月10䞇皌いでいたころ、私は「なんで昚日の投皿が䞊がっおないんだろう」を手動確認で発芋する毎日でした。掛け持ちで月60䞇になっおからも、同じ問題が繰り返し起きおいたした。䌚瀟郜合で解雇されお0になり、半幎かけお Claude Code の自埋環境を䞀から建お盎しお今の月商120䞇に至るわけですが、この過皋で孊んだこずが䞀぀ありたす。「止たっおいるこずに気づかない」状態を攟眮するず、皌ぎではなく信頌が消えるずいうこずです。

仕組みの栞心は「発火した瞬間に止める・知らせる・残す」の䞉点です。クォヌタに圓たったら、以降の党 Claude 呌び出しを即座にブロックexit 75し、Discord に通知し、JSON ファむルにサヌキット状態を氞続化する。手動で確認しに行くのではなく、環境偎から叫んでもらいたす。

この考え方は電気回路のサヌキットブレヌカヌず同じです。過電流が流れた瞬間に遮断噚が萜ちる。埩旧したら手動でリセットする。ゞョブが「無駄に䜕床も叩いお゚ラヌを積む」状態を防ぎ぀぀、状態が倖から芳枬できる圢で止たりたす。


党䜓の流れ

アヌキテクチャ抂芁

LaunchAgent (定期ゞョブ矀 × 15本)
      │
      │  CLAUDE=~/.claude/scripts/claude-quota-guard.py
      ▌
claude-quota-guard.py
      │
      ├─[CLOSED]─▶ ~/.local/bin/claude (本物) を実行
      │                    │
      │              stdout + stderr の末尟 128KB をスキャン
      │                    │
      │           "weekly usage limit" 等 7パタヌンに䞀臎
      │                    │
      │              YES ───
      │                    â–Œ
      │         open_until = now + 21600s (6時間)
      │         reason = "quota-message"
      │         ~/.claude/state/claude-quota-circuit.json に原子曞き蟌み
      │         ~/.discord/notify.sh "alerts" ぞ即時通知
      │
      └─[OPEN]──▶ exit 75 で即ブロック盎接呌び出し時
                  exit 0 でスキップ--job モヌド時

埩旧フロヌ:
  制限リセット埌 ─▶ claude-quota-guard.py --reset ─▶ CLOSED に戻る
  たたは open_until を過ぎるず自動で CLOSEDnormalize_expired

サヌキット状態は ~/.claude/state/claude-quota-circuit.json に原子的曞き蟌みで氞続化されたす。耇数のゞョブが同時に状態を読み曞きしおも砎損しないよう、fcntl.LOCK_EX でファむルロックをかけおから曎新し、os.replace() でアトミックに眮き換えたす。

クォヌタパタヌンの怜出

claude-quota-guard.py がクォヌタず刀断する文字列は7皮類ありたす。

QUOTA_PATTERNS = (
    r"weekly (?:usage )?limit",
    r"usage limit",
    r"rate limit",
    r"quota (?:exceeded|limit|reached)",
    r"(?:you(?:'ve| have) )?hit your limit",
    r"limit reached",
    r"resets? (?:at|in|on|tomorrow)",
)

倧文字・小文字を区別せずre.IGNORECASE、Claude 本䜓の stdout ず stderr を合わせた末尟 131,072 バむト128KBを察象にスキャンしたす。

combined = (result.stdout + b"\n" + result.stderr)[-131072:].decode("utf-8", errors="replace")
record_claude_result(result.returncode, combined)

末尟を切り取るのは明確な理由がありたす。長時間皌働するゞョブの序盀の正垞ログを読み飛ばし、゚ラヌが出やすい末尟だけを効率的に怜査するためです。巚倧な stdout をすべおメモリに展開せずに枈むずいう副次効果もありたす。

サヌキットが開く二぀のトリガヌ

クォヌタメッセヌゞ怜出以倖にも、もう䞀぀のトリガヌがありたす。10分以内に exit 1 が3回続くず匷制 OPEN になりたす。

cooldown  = int(os.environ.get("CLAUDE_GUARD_COOLDOWN_SECONDS", "21600"))
window    = int(os.environ.get("CLAUDE_GUARD_FAILURE_WINDOW_SECONDS", "600"))
threshold = int(os.environ.get("CLAUDE_GUARD_FAILURE_THRESHOLD", "3"))

デフォルト倀は cooldown=216006時間・window=60010分・threshold=33回です。Claude の週次制限は日本時間の早朝にリセットされる堎合が倚く、6時間埌には制限が明ける可胜性が高いずいう経隓則からこの倀にしおいたす。環境倉数で䞊曞きできるので、自分の制限リセットタむミングに合わせお調敎できたす。

トリガヌ別に reason フィヌルドが蚘録されたす。クォヌタメッセヌゞ怜出なら "quota-message"、連続倱敗なら "repeated-exit-1" です。この区別が埌の埩旧刀断に効いおきたす。

OPEN 時の挙動──静かに止める

サヌキットが OPEN の状態で呌ばれるず、run_claude は即座に exit 75 を返したす。

EXIT_CIRCUIT_OPEN = 75

def run_claude(arguments: list[str]) -> int:
    status = circuit_status()
    if status["is_open"]:
        print(
            "CLAUDE_QUOTA_CIRCUIT_OPEN "
            f"reason={status['reason']} remaining={status['remaining_seconds']}s",
            file=sys.stderr,
        )
        return EXIT_CIRCUIT_OPEN
    # 以䞋、本物の claude を実行する凊理

exit 75 は POSIX 慣䟋の EX_TEMPFAIL䞀時的な倱敗に察応する倀で、「今は無理だが埌で詊せる」を意味したす。launchd の plist で <key>SuccessfulExit</key><true/> を蚭定しおいれば exit 0 のみを成功扱いにする蚭定もありたすが、exit 75 を別途 AbandonProcessGroup ず組み合わせるこずで「クォヌタ䞭は静かに諊める」挙動を plist 偎で明瀺的にコントロヌルできたす。

--job モヌドはさらに穏やかです。OPEN のずきは return 0正垞終了を返しおゞョブをスキップしたす。ゞョブスクリプト偎は Claude が呌ばれたこずすら知らずに終わりたす。

def run_job(label: str, command: list[str]) -> int:
    status = circuit_status()
    if status["is_open"]:
        print(
            "CLAUDE_QUOTA_JOB_SKIPPED "
            f"job={label} reason={status['reason']} remaining={status['remaining_seconds']}s",
            file=sys.stderr,
        )
        return 0
    # 以䞋、CLAUDE_AUTOMATION_GUARD=1 を env に枡しおゞョブを実行

Discord 通知──止たったこずを倖から知る

サヌキットが OPEN になった瞬間、notify_circuit_open() が ~/.discord/notify.sh を叩きたす。

def notify_circuit_open(reason: str, cooldown: int) -> None:
    """circuit OPENは15job䞀斉の6hサむレント停止になる — 必ずDiscordぞ可芖化する。"""
    script = Path.home() / ".discord" / "notify.sh"
    if not script.exists():
        return
    hours = round(cooldown / 3600, 1)
    subprocess.run(
        [str(script), "alerts",
         f"🚚 claude-quota-guard circuit OPEN (reason={reason}) "
         f"— Claude生成ゞョブを{hours}hスキップしたす。"
         f"埩旧枈みなら `claude-quota-guard.py --reset`"],
        check=False, timeout=10, capture_output=True,
    )

コメントに「15job䞀斉の6hサむレント停止」ず曞いおあるのが私の環境の実態です。クヌルダりン秒数を時間に䞞めおround(cooldown / 3600, 1)メッセヌゞに埋め蟌むこずで、スマホの Discord 通知を芋た瞬間に「あず䜕時間埅おばいいか」がわかりたす。timeout=10 で Discord ぞの通知自䜓が詰たっおもゞョブ党䜓を止めない蚭蚈になっおいたす。

health チェックずの統合

automation-health.sh はセクション 1.5「Claude生成quota circuit」でサヌキット状態を毎回確認したす。

quota_guard="$CLAUDE/scripts/claude-quota-guard.py"
quota_state=$("$quota_guard" --status 2>/dev/null || true)
quota_open=$(printf '%s' "$quota_state" | jq -r '.is_open // false')
if [ "$quota_open" = "true" ]; then
    quota_reason=$(printf '%s' "$quota_state" | jq -r '.reason // "unknown"')
    quota_remaining=$(printf '%s' "$quota_state" | jq -r '.remaining_seconds // 0')
    wn "OPEN: Claude生成15jobをskipäž­ / reason=$quota_reason / remaining=${quota_remaining}s"
elif [ -n "$quota_state" ]; then
    ok "CLOSED: Claude生成jobは実行可胜"
else
    ng "quota guard status の取埗倱敗"
fi

--status は珟圚の JSON をそのたた stdout に出力したす。is_open ず remaining_seconds はランタむムで蚈算されるフィヌルドで、ファむルに保存された open_until から珟圚時刻を匕いお毎回算出したす。

def circuit_status() -> dict:
    timestamp = now()
    with locked_state() as state:
        normalize_expired(state, timestamp)
        result = dict(state)
    result["is_open"] = int(result.get("open_until", 0) or 0) > timestamp
    result["remaining_seconds"] = max(0, int(result.get("open_until", 0) or 0) - timestamp)
    return result

normalize_expired() は open_until が珟圚時刻を過ぎおいれば自動で CLOSED に戻したす。぀たり --reset を打たなくおも、6時間埌には自然にサヌキットが閉じたす。

実際の状態ファむル

この蚘事を曞いおいる珟時点のサヌキット状態は次のずおりです。

{
  "consecutive_failures": [],
  "last_success": 1785280668,
  "open_until": 1785470423,
  "opened_at": 1785448823,
  "reason": "quota-message",
  "version": 1
}

opened_at から open_until を匕くず 21,600 秒6時間ちょうどです。reason は "quota-message" なので、Claude 本䜓が返した出力の䞭に7パタヌンのいずれかが含たれおいたこずを瀺しおいたす。last_success は最埌に Claude の呌び出しが正垞終了したタむムスタンプで、これが叀くなるほど「ゞョブが長期間スキップされおいる」サむンになりたす。consecutive_failures が空配列なのは、クォヌタメッセヌゞ怜出でサヌキットが開いた堎合、連続倱敗カりンタヌをリセットする仕様だからです。

if quota_message(output):
    state["open_until"] = timestamp + cooldown
    state["reason"] = "quota-message"
    state["opened_at"] = timestamp
    state["consecutive_failures"] = []   # ← 連続倱敗カりンタヌをクリア
    opened_reason = "quota-message"

二぀のトリガヌが同時に走るこずはなく、クォヌタメッセヌゞが優先しお評䟡される蚭蚈になっおいたす。

実装の詳现

ファむルロックず原子曞き蟌み──15本が同時に螏んでも壊れない

䞊走する15本のゞョブが同時に claude-quota-circuit.json を読み曞きしたずき、JSONが砎損する最悪ケヌスを防ぐのが locked_state() コンテキストマネヌゞャです。Pythonの threading.Lock は同䞀プロセス内しか制埡できないので、別プロセス間を仕切るOSレベルのファむルロックfcntl.LOCK_EXを䜿っおいたす。

@contextmanager
def locked_state():
    path = state_path()
    path.parent.mkdir(parents=True, exist_ok=True)
    lock_path = path.with_suffix(path.suffix + ".lock")
    with lock_path.open("a+") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        state = default_state()
        if path.exists():
            try:
                loaded = json.loads(path.read_text(encoding="utf-8"))
                if isinstance(loaded, dict):
                    state.update(loaded)
            except (OSError, ValueError):
                pass
        yield state
        # ... 曞き蟌みフェヌズ
        fcntl.flock(lock.fileno(), fcntl.LOCK_UN)

蚭蚈䞊のポむントが䞉぀ありたす。

ロックはサむドカヌファむル .json.lock に掛ける。本䜓JSONに盎接ロックをかけるず、曞き蟌み途䞭のバむト列を別プロセスが読む䜙地が生たれたす。ロック甚ファむルを分離するこずで、本䜓JSONの読み曞きを垞にロック保持䞭の安党な窓の䞭に収めたす。path.with_suffix(path.suffix + ".lock") は claude-quota-circuit.json.lock ずいうパスを生成したす。

曞き蟌みは os.replace() でアトミックに。同ディレクトリ内に tempfile.mkstemp で䞀時ファむルを䜜り、json.dump しおから os.replace(temp_name, path) で眮き換えたす。同䞀ファむルシステム䞊のrenameはPOSIXでアトミックなので、読み途䞭のJSONが生たれたせん。

fd, temp_name = tempfile.mkstemp(prefix=path.name + ".", dir=path.parent)
try:
    with os.fdopen(fd, "w", encoding="utf-8") as temp:
        json.dump(state, temp, ensure_ascii=False, sort_keys=True)
        temp.write("\n")
    os.replace(temp_name, path)
finally:
    if os.path.exists(temp_name):
        os.unlink(temp_name)

finally でテンポラリの残骞も消したす。os.replace が倱敗した堎合でも䞀時ファむルが残り続けないようにしおありたす。

状態は default_state() でベヌスを䜜り update() で補完。ファむルが存圚しない初回でも、JSONが壊れおいおKeyErrorが出そうな状況でも、デフォルト倀を持぀蟞曞に察しお update() を圓おおいるのでKeyErrorが起きたせん。壊れたJSONを読んだOSError・ValueErrorは pass で黙っお流し、デフォルト倀で䞊曞きしたす。「壊れたら䜕もしない」より「壊れたらデフォルトに戻す」の方がサヌキットずしお堅牢です。

本物の claude を探す──自己参照ガヌド

claude-quota-guard.py はシェルから芋るず claude コマンドの代替ずしお配眮されるラッパヌです。本物のバむナリを呌び出すのが real_claude() 関数の仕事です。

def real_claude() -> str:
    configured = os.environ.get("CLAUDE_REAL_BIN")
    if configured:
        return str(Path(configured).expanduser())
    candidate = Path.home() / ".local" / "bin" / "claude"
    try:
        resolved = candidate.resolve(strict=True)
    except OSError:
        return str(candidate)
    if resolved == Path(__file__).resolve():
        raise RuntimeError("CLAUDE_REAL_BIN points back to claude-quota-guard.py")
    return str(resolved)

CLAUDE_REAL_BIN 環境倉数が蚭定されおいればそちらを優先し、なければ ~/.local/bin/claude のシンボリックリンクを resolve(strict=True) で実䜓解決したす。そしお解決先が自分自身ず䞀臎しないかを比范しおいたす。Path(__file__).resolve() がスクリプト自身の絶察パスです。䞀臎すれば RuntimeError を投げお即停止したす。これは私が実際に螏んだフォヌク爆匟の察策で、埌述したす。

run_job() の環境倉数むンゞェクション

--job LABEL -- COMMAND [ARG...] モヌドでは、ラップするコマンドを実行する前に環境倉数を䞉本泚入したす。

env = os.environ.copy()
env["CLAUDE_AUTOMATION_GUARD"] = "1"
env["CLAUDE"] = guard       # claude-quota-guard.py の絶察パス
env["CLAUDE_BIN"] = guard

CLAUDE_AUTOMATION_GUARD=1 はゞョブスクリプト偎が「自動実行から呌ばれた」ず刀断するためのフラグです。CLAUDE ず CLAUDE_BIN をガヌド自身のパスで䞊曞きするこずで、ゞョブが内郚で $CLAUDE・$CLAUDE_BIN を呌ぶず再びガヌドを経由したす。ゞョブが䜕十回 claude を呌がうずも、すべおの呌び出しがサヌキットチェックを通過する蚭蚈です。ただし $PATH 䞊の claude を盎接呌ぶ堎合はこの泚入が効きたせん。私の環境では $PATH 䞊の claude もガヌドぞのシンボリックリンクに統䞀しおいたす。

テスト甚の時刻泚入──CLAUDE_GUARD_NOW

6時間埌にサヌキットが自動で CLOSED になる挙動を実際に6時間埅たずに怜蚌するため、now() 関数は環境倉数 CLAUDE_GUARD_NOW を参照したす。

def now() -> int:
    return int(os.environ.get("CLAUDE_GUARD_NOW", str(int(time.time()))))

珟圚の claude-quota-circuit.json の open_until は 1785470423 です。以䞋を実行するず「1秒過ぎた瞬間」をシミュレヌトできたす。

CLAUDE_GUARD_NOW=1785470424 \
  ~/.claude/scripts/claude-quota-guard.py --status | jq .

is_open: false・remaining_seconds: 0 が返っおきたす。本番の状態ファむルを曞き換えずに時刻を動かせるので、launchd から実際に呌ばれる文脈でも安党にテストできたす。--open SECONDS REASON サブコマンドず組み合わせれば、任意の状態を䜜っおから CLAUDE_GUARD_NOW で時間を進める䞀連のテストが可胜です。


私が詰たった話

①「フォヌク爆匟」──ラッパヌが自分自身を呌び続けた

最初のセットアップ時、既存の ~/.local/bin/claude バむナリのパスを確認せずにガヌドスクリプトぞのシンボリックリンクに曞き換えたした。

症状: claude を叩いた瞬間にタヌミナルが固たりたした。別りィンドりで ps aux | grep python を確認するず、同䞀スクリプトが数十プロセス䞊走しおいたす。MacのActivity Monitorでは1〜2秒でPythonプロセスが雪厩のように増え、CPU䜿甚率が100%に匵り付きたした。

原因: ~/.local/bin/claude → claude-quota-guard.py ずいうリンクを䜜ったため、real_claude() が ~/.local/bin/claude を解決するず自分自身claude-quota-guard.pyに戻っおきたす。子プロセスずしお自分を起動し、その子がたた自分を起動するフォヌク爆匟です。resolve(strict=True) 自己参照チェックは埌から远加された察策ですが、圓時はこのチェックがなかったため無限ルヌプが止たりたせんでした。

盎し方: たず killall python3 でプロセスを匷制終了し、CLAUDE_REAL_BIN を明瀺するだけです。

# ~/.zshrc に远蚘しおシェルを再起動
export CLAUDE_REAL_BIN="$HOME/.nvm/versions/node/v24.13.0/bin/claude"

珟圚のコヌドにある自己参照チェックは、この倱敗を螏んだ埌に远加したした。CLAUDE_REAL_BIN を最初から蚭定しおおけばチェックを経由しないので、セットアップ手順の最初のステップをこれにするのが最も確実です。

②「ロックファむル残骞」──党ゞョブがハングした朝

症状: ある朝から automation-health.sh のセクション 1.5「Claude生成quota circuit」が数十秒間返っおこなくなりたした。quota_guard --status を手動で叩いおもハング。

原因: 前倜、私が kill -9 でプロセスを匷制終了したずき、automation-health.sh の倚重起動ガヌドが䜿う /tmp/automation-health.lock/pid に叀いPIDが残留しおいたした。

# automation-health.sh の倚重起動ガヌド抜粋
if ! mkdir "$_ah_lock" 2>/dev/null; then
  if kill -0 "$(cat "$_ah_lock/pid" 2>/dev/null)" 2>/dev/null; then
    echo "automation-health: 別むンスタンス皌働䞭のためスキップ" >&2
    exit 0   # ← ここで黙っお終了しおいた
  fi
  rm -rf "$_ah_lock"
  ...
fi

kill -0 はプロセスの存圚確認です。kill -9 で殺したプロセスのPIDが、その埌に起動した党く別のプロセスに再利甚されるず、kill -0 が成功しおしたいたす。「別むンスタンス皌働䞭」ず誀認しお exit 0 で黙っお終わるため、呌び出し元から芋るず「health checkが䞀瞬で終わっおOKを返した」に芋えたす。実際には䞀床も走っおいたせん。これが「党ゞョブが問題なく動いおいるように芋えるのに実態は誰も確認しおいない」ずいう最悪のケヌスです。

盎し方: /tmp/automation-health.lock を手動で削陀するだけです。

rm -rf /tmp/automation-health.lock

kill -9 を䜿わず、必ず Ctrl+CSIGINTか kill -TERM で終了するようにしたした。trap EXIT が走れば rm -rf "$_ah_lock" でロックが解攟されたす。kill -9 は「どうしおも終わらないずき」だけの最終手段で、䜿った埌は必ず /tmp/automation-health.lock を手動で掃陀したす。

③「repeated-exit-1 の誀発火」──Wi-Fi 䞍調がサヌキットを開けた

クォヌタずは無関係な状況でサヌキットが OPEN になり、翌朝たで15本のゞョブがスキップされおいた事䟋です。

症状: Discord に reason=repeated-exit-1 の通知が来たした。その時点で週次クォヌタは十分残っおいたはずです。

原因: その倜、Wi-Fi ルヌタヌを再起動しおいたした。10分CLAUDE_GUARD_FAILURE_WINDOW_SECONDS=600以内に Claude の呌び出しが3回CLAUDE_GUARD_FAILURE_THRESHOLD=3exit 1 で倱敗すれば、理由に関係なくサヌキットが開きたす。

failures = [
    int(value)
    for value in state.get("consecutive_failures", [])
    if timestamp - int(value) <= window   # 10分以内の倱敗だけ残す
]
failures.append(timestamp)
state["consecutive_failures"] = failures
if len(failures) >= threshold:            # 3回以䞊でOPEN
    state["open_until"] = timestamp + cooldown
    state["reason"] = "repeated-exit-1"

ネットワヌク障害は exit 1 を返したす。15本䞭3本が先にこの条件を螏んでサヌキットが開いた瞬間、残り12本はゞョブスキップexit 0に切り替わりたす。

盎し方: reason=repeated-exit-1 は翌朝にクォヌタ制限が原因ではないずわかった時点で即 --reset を打おばよいので、Discord 通知に repeated-exit-1 ず quota-message で文面を倉えるこずを怜蚎したした。しかし最終的には「理由を問わず OPEN になったら人間が刀断しお --reset を打぀」運甚に統䞀しおいたす。自動 --reset にするずネットワヌク障害䞭でも Claude を叩こうずしお倱敗ルヌプが続くからです。通知メッセヌゞには既に 埩旧枈みなら claude-quota-guard.py --reset ず曞いおありたす。

誀発火を枛らしたい堎合は環境倉数で閟倀を䞊げられたす。

export CLAUDE_GUARD_FAILURE_THRESHOLD=5

ただし私の環境では「5回以䞊exit 1が続く確かに䜕かおかしい」ずいう刀断で3のたたにしおいたす。

④「--reset を忘れおたる䞀日スキップした」

クォヌタ制限が明けた埌も --reset を打たずに攟眮した倱敗です。

症状: その日の深倜に automation-health.sh を走らせるず OPEN: Claude生成15jobをskipäž­ / remaining=3600s が出おいたした。䞀日分のゞョブが党郚飛んでいたす。

原因: open_until の蚈算を誀解しおいたした。クォヌタのリセットタむミングず6時間クヌルダりンは完党に独立しおいたす。日曜22時にサヌキットが開けば open_until は月曜4時です。週次クォヌタが月曜3時にリセットされおいおも、サヌキットは4時たで OPEN のたたです。「クォヌタが明けたらサヌキットも自動で戻る」わけではありたせん。normalize_expired() が open_until を過ぎおいたら自動で CLOSED に戻しおくれたすが、その open_until はあくたで opened_at + 21600 です。

盎し方: 埩旧ワンラむナヌを .zshrc に゚むリアスずしお登録したした。

alias claude-recover='~/.claude/scripts/claude-quota-guard.py --reset && \
  bash ~/.claude/scripts/automation-health.sh'

Discord でクォヌタ䞊限通知を受け取ったらその堎でスレッドにリマむンダヌを立おおおき、翌朝 claude-recover を打぀ワヌクフロヌが定着しおいたす。--reset 盎埌に automation-health.sh をセットで走らせるこずで、CLOSED に戻ったこずず15本の launchd ゞョブが正垞皌働しおいるこずを䞀床に確認できたす。

remaining_seconds の倀はスマホの Discord からも読めるので、OPEN を怜知したら残り秒数をそのたた日皋アプリのリマむンダヌに倉換するショヌトカットも蚭定したした。「あず21600秒埌に確認」ではなく「月曜朝4時にアラヌム」です。自動化が止たっおいる時間を知っおいるず、手動でカバヌすべき䜜業を刀断できたす。

぀たずきポむント実運甚で螏んだ地雷リスト

前段ではフォヌク爆匟・ロックファむル残骞・repeated-exit-1 誀発火・--reset 忘れの4件を取り䞊げたした。ここではそれ以倖に実運甚で螏んだ地雷を網矅したす。

① --job の -- 区切りを忘れるず exit 2 でサむレント党滅する

claude-quota-guard.py --job LABEL -- COMMAND [ARG...] の -- を曞き忘れお --job note-autolike bash /path/to/run.sh ず曞くず、コヌド内の刀定 arguments[2] != "--" が True になり即 exit 2 で終わりたす。

if len(arguments) < 4 or arguments[2] != "--":
    print("usage: claude-quota-guard.py --job LABEL -- COMMAND [ARG...]", file=sys.stderr)
    return 2

launchd は last exit=2 ず蚘録するだけで次のサむクルも再実行しようずしたす。automation-health.sh のセクション 1launchd バッチが RED ずしお拟いたすが、「なぜ exit 2 なのか」は /tmp/com.shun.xxxx.stderr.log を盎接開くたでわかりたせん。新しい plist を launchctl load する前に、手動で1回ロヌカル実行しお exit code を確認する習慣がなければ、デプロむ初日から党滅したす。

② launchd は ~/.zshrc を読たないため $CLAUDE が空になる

run_job() は子プロセスに CLAUDE ず CLAUDE_BIN をガヌド自身のパスで䞊曞きしたす。しかしその**芪プロセスlaunchd ゞョブ自䜓**に $CLAUDE が枡っおいなければ意味がありたせん。~/.zshrc で export CLAUDE=~/.claude/scripts/claude-quota-guard.py をしおいおも、launchd 起動のゞョブはその蚭定を芋たせん。空の $CLAUDE を展開したシェルスクリプトが $CLAUDE --print "..." を実行しようずしお exit 127 → 10分以内に3回続けば repeated-exit-1 でサヌキットが開きたす。クォヌタずは無関係な誀発火です。党 plist の <key>EnvironmentVariables</key> セクションに盎曞きするのが唯䞀確実な解です。

<key>EnvironmentVariables</key>
<dict>
  <key>CLAUDE_REAL_BIN</key>
  <string>~/.nvm/versions/node/v24.13.0/bin/claude</string>
  <key>CLAUDE</key>
  <string>~/.claude/scripts/claude-quota-guard.py</string>
  <key>CLAUDE_BIN</key>
  <string>~/.claude/scripts/claude-quota-guard.py</string>
</dict>

③ stdout が倧きいゞョブでクォヌタメッセヌゞが 128KB 末尟の倖に抌し出される

record_claude_result がスキャンするのは stdout ず stderr を合わせた末尟 131,072 バむト128KBです。

combined = (result.stdout + b"\n" + result.stderr)[-131072:].decode("utf-8", errors="replace")

数䞇行のファむルをそのたた --print に流し蟌むゞョブや、長倧なドキュメントを出力するゞョブでは、正垞出力が 128KB を超えおクォヌタメッセヌゞが末尟から倖れるこずがありたす。その堎合 quota-message ずしお怜知されず、3回 exit 1 を繰り返しおようやく repeated-exit-1 で開きたす。その間の2回分のゞョブは「静かに倱敗しおいる」期間ずしお存圚したす。入力が 100KB を超えるゞョブは 50KB 前埌に分割するか、䞭間ファむルに萜ずしおから Claude に枡す蚭蚈に切り替えたした。

④ jq が未むンストヌルだず health チェックが誀 RED を出す

automation-health.sh のセクション 1.5 は jq で JSON を解析したす。

quota_open=$(printf '%s' "$quota_state" | jq -r '.is_open // false' 2>/dev/null)

jq がなければ quota_open が空文字列になり、ng "quota guard status の取埗倱敗" が出お exit 1 したす。ガヌド自䜓は正垞に動いおいるのに health が RED になるずいう混乱が生たれたす。brew install jq で即解決ですが、「サヌキット状態の取埗に倱敗しおいる」ず誀解しおセットアップをやり盎す前に which jq を確認しおください。

â‘€ ~/.discord/notify.sh が存圚しないずサヌキット開攟が完党に無音になる

def notify_circuit_open(reason: str, cooldown: int) -> None:
    script = Path.home() / ".discord" / "notify.sh"
    if not script.exists():
        return  # ゚ラヌなし・ログなし・通知なし

notify.sh が存圚しなければ関数は䜕もせず戻りたす。サヌキット自䜓は正垞に開き状態ファむルにも蚘録されたすが、倖から知る手段がれロになりたす。この仕組みを入れた意矩が半分消えたす。私は最初の2週間この蚭定を怠り、朝に automation-health.sh を走らせるたで前倜のクォヌタ超過に気づかなかったこずが3回ありたした。既補スクリプトがなければ Webhook URL ぞの curl を1行曞くだけで動きたす。Discord Webhook のセットアップは15分で終わりたす。環境構築圓日に枈たせおください。

⑥ CLAUDE_GUARD_COOLDOWN_SECONDS を短くしすぎおサヌキットが途䞭で閉じる

CLAUDE_GUARD_COOLDOWN_SECONDS=3600 に蚭定した堎合、日曜22時にサヌキットが開いおも月曜0時には CLOSED に戻りたす。週次クォヌタが月曜3時にリセットされる環境なら、0〜3時の間にゞョブが再実行されお倱敗を繰り返し、repeated-exit-1 で再び OPEN ずいうルヌプに入りたす。珟圚のサヌキット状態ファむルを確認するず opened_at=1785448823・open_until=1785470423 の差分はちょうど 21,600 秒6時間です。この倀は1週間の芳枬でリセットタむミングを実枬しおから決めおいたす。デフォルト 21,600 秒を倉えるなら必ず同じプロセスを螏んでください。

⑩ CLAUDE_GUARD_NOW をテスト埌に .zshrc に残すず本番の時刻刀定が壊れる

テスト目的で .zshrc に export CLAUDE_GUARD_NOW=1785470424 ず曞いた埌に削陀を忘れるず、党おの now() 呌び出しがそのタむムスタンプを返し続けたす。

def now() -> int:
    return int(os.environ.get("CLAUDE_GUARD_NOW", str(int(time.time()))))

未来の倀ならサヌキットが開かないopen_until ずの比范で垞に CLOSED 刀定、過去の倀なら normalize_expired() が垞時 CLOSED に戻すずいう状況になりたす。テスト埌は必ず unset CLAUDE_GUARD_NOW しおください。時刻操䜜はコマンドの前眮き圢匏で枡すのが安党です。

CLAUDE_GUARD_NOW=1785470424 ~/.claude/scripts/claude-quota-guard.py --status | jq .

この圢匏なら .zshrc には残りたせん。

⑧ CLAUDE_AUTOMATION_GUARD=1 チェックを入れおいないゞョブを手動実行しお本番デヌタを䞊曞きした

run_job() が泚入する CLAUDE_AUTOMATION_GUARD=1 は、ゞョブスクリプト自身がチェックしなければ意味がありたせん。デバッグ目的で bash run.sh を盎接叩くず、launchd 経由ず同じ動䜜をしたす。実際に note-autolike のスクリプトをデバッグ実行したずき、本番の自動いいねが40件走りたした。重芁ゞョブのスクリプト先頭には必ず以䞋を入れおいたす。

[ "${CLAUDE_AUTOMATION_GUARD:-0}" = "1" ] || { echo "自動実行専甚です" >&2; exit 1; }

SNS 投皿・API 曞き蟌み・ファむル䞊曞きを䌎う党ゞョブに適甚しおいたす。

⑹ last_success の叀さに気づかず長期スキップを芋逃す

サヌキットが repeated-exit-1 で開いた堎合、consecutive_failures はリセットされたすが last_success はそのたた保持されたす。珟圚の状態ファむルを確認するず last_success=1785280668・opened_at=1785448823 で、差分は 168,155 秒、玄 46.7 時間です。サヌキットが開く玄2日前から Claude の正垞終了が1床もなかったこずになりたす。この倀を定期監芖しおいれば「サヌキットが開く前から䜕かがおかしかった」ず事前に気づけたす。--status で取埗できる倀なので、automation-health.sh の次の改善ずしお远加する予定です。


ベストプラクティス

1. セットアップ第䞀歩は CLAUDE_REAL_BIN の蚭定

フォヌク爆匟の防止策はただ䞀点です。~/.local/bin/claude をガヌドぞのシンボリックリンクに曞き換える前に、必ず CLAUDE_REAL_BIN を蚭定しおください。~/.zshrc ず党 plist の EnvironmentVariables の䞡方に曞かないず launchd ゞョブで効きたせん。

2. plist の EnvironmentVariables に䟝存倉数を党お盎曞きする

CLAUDE_REAL_BIN・CLAUDE・CLAUDE_BIN・CLAUDE_GUARD_COOLDOWN_SECONDS・CLAUDE_GUARD_FAILURE_THRESHOLD。これらを ~/.zshrc に曞いただけでは launchd ゞョブに届きたせん。シェルラッパヌ経由でも動きたすが、䟝存が増えお障害点が増えたす。plist 盎曞きが最も確実です。

3. jq のむンストヌルを先に確認する

which jq || brew install jq を環境構築手順の最初のステップにしおください。automation-health.sh の JSON 解析は党お jq に䟝存しおいたす。

4. Discord 通知を初日に蚭定する

~/.discord/notify.sh が存圚しなければサヌキット開攟は完党無音です。Webhook URL ぞの curl を1行曞くだけで動きたす。これを埌回しにするず「止たっおいるこずに気づかない」ずいう元の問題に戻りたす。

5. ゞョブスクリプトの先頭に CLAUDE_AUTOMATION_GUARD=1 チェックを入れる

[ "${CLAUDE_AUTOMATION_GUARD:-0}" = "1" ] || { echo "自動実行専甚です" >&2; exit 1; }

手動デバッグ実行での本番操䜜事故を防ぎたす。API 曞き蟌み・SNS 投皿・ファむル䞊曞きを䌎う党ゞョブに適甚しおください。

6. --job の plist は launchctl load 前にロヌカルで手動テストする

python3 ~/.claude/scripts/claude-quota-guard.py --job LABEL -- bash script.sh を手動で1回走らせお exit code を確認しおください。-- の抜け・ラベルのタむポ・パスの誀りはここで党郚拟えたす。exit 2 であれば構文ミスです。

7. claude-status ゚むリアスで毎朝の確認を1行にする

alias claude-status='~/.claude/scripts/claude-quota-guard.py --status | \
  jq -r "if .is_open then \"🔎 OPEN reason=\(.reason) remaining=\(.remaining_seconds | . / 3600 | floor)h\" else \"🟢 CLOSED\" end"'

Discord 通知が飛ばなかった堎合の保険ずしお、朝の確認ルヌティンに組み蟌んでください。

8. claude-recover は --reset ず automation-health.sh をセットにする

--reset 単䜓では「CLOSED になったか」ず「15本のゞョブが正垞再開したか」の䞡方が確認できたせん。

alias claude-recover='~/.claude/scripts/claude-quota-guard.py --reset && \
  bash ~/.claude/scripts/automation-health.sh'

この゚むリアス1本で埩旧ず確認を䞀気に枈たせたす。

9. CLAUDE_GUARD_FAILURE_THRESHOLD をネットワヌク環境に合わせる

デフォルト 310分以内に3回 exit 1は Wi-Fi 䞍安定環境では誀発火しやすいです。VPN 切断が頻繁な堎合は CLAUDE_GUARD_FAILURE_THRESHOLD=5 か、りィンドりを CLAUDE_GUARD_FAILURE_WINDOW_SECONDS=3005分に絞るこずを怜蚎しおください。ただし感床を䞋げすぎるず本物のクォヌタ超過の怜知が遅れたす。自分の環境の障害パタヌンを1週間芳察しおから調敎しおください。

10. クヌルダりン倀は実枬から決める

Claude Code の週次クォヌタのリセットタむミングは公匏に明瀺されおいたせん。1週間、サヌキットが開いた時刻ず次の正垞応答時刻を蚘録しお平均を取っおから CLAUDE_GUARD_COOLDOWN_SECONDS を蚭定しおください。デフォルト 21,600 秒6時間を倉えるなら根拠を持っお倉えおください。

11. last_success の叀さを定期監芖する

24時間以䞊 last_success が曎新されおいなければ䜕かがおかしいサむンです。automation-health.sh に以䞋のチェックを远加するず事前怜知できたす。

last_success=$(printf '%s' "$quota_state" | jq -r '.last_success // 0')
gap=$(( $(date +%s) - last_success ))
[ "$gap" -gt 86400 ] && wn "last_success が $((gap/3600))h 前 — Claude呌び出し成功が24h以䞊ない"

12. 入力が倧きいゞョブは分割しお 128KB 制玄を避ける

クォヌタメッセヌゞが末尟 128KB 倖に抌し出されるず repeated-exit-1 刀定になりたす。入力ファむルが 100KB を超えるゞョブは 50KB ず぀分割するか、芁玄→詳现の2段階凊理にしお各呌び出しの出力サむズを抑えおください。

13. CLAUDE_GUARD_NOW はコマンド前眮きで枡す・.zshrc に曞かない

テスト甚の時刻泚入は CLAUDE_GUARD_NOW=xxx ~/.claude/scripts/claude-quota-guard.py --status の圢で枡しおください。.zshrc に曞くず本番環境に残留しおサヌキットの時刻刀定が狂いたす。

14. automation-health.sh 自䜓を launchd で毎朝定期実行する

手動確認を習慣に頌るのは危険です。com.shun.daily-health-check.plist ずしお毎朝6時に実行し、RED があれば Discord に通知するラッパヌを噛たせれば確認挏れがれロになりたす。私の環境では health check の結果も alerts チャンネルに流しおいたす。

15. --reset は状態を確認しおから打぀

--reset を打぀前に claude-statusたたは --status | jq .で reason ず remaining_seconds を確認しおください。reason=quota-message で残り時間が数千秒残っおいる堎合、クォヌタが本圓に明けおいるかを確かめおから打぀刀断が必芁です。早すぎる --reset → 党ゞョブ䞀斉再詊行 → 連続 exit 1 → 再び repeated-exit-1 OPEN ずいうルヌプを避けるための䞀手間です。


たずめ

クォヌタに圓たったずき、「䜕が起きおいるかを環境が叫んでくれる」状態を䜜るのがこの仕組みの党䜓像です。

claude-quota-guard.py がクォヌタメッセヌゞを怜知しおサヌキットを OPEN にする。notify_circuit_open() が ~/.discord/notify.sh を叩いお alerts チャンネルに通知する。翌朝の automation-health.sh がセクション 1.5 で WARN ずしお拟う。claude-recover ゚むリアスで --reset ず health チェックをセットで実行しお埩旧を確認する。この䞀連のフロヌが確立されれば、「止たっおいるこずに気づかない」状態は存圚しなくなりたす。

珟圚のサヌキット状態ファむルreason=quota-message・opened_at=1785448823・open_until=1785470423は、この仕組みが実際に発動した蚌拠です。last_success=1785280668 から opened_at たでの差は玄 46.7 時間であり、これは「サヌキットが開く前から䜕かが静かにおかしかった」こずを瀺しおいたす。この芳察自䜓が次の改善ポむントを瀺しおくれたす。

月120䞇の自動化基盀は「動いおいる」こずを前提に皌いでいるわけではなく、「止たったずきに即座に怜知しお最短で埩旧できる」構造で皌いでいたす。クォヌタは必ず圓たりたす。問題は圓たったずきに䜕が起きるかです。


仕組みの党䜓像・月120䞇の内蚳・30日手順は有料noteにたずめおいたす。
📕 Claude Code自埋環境で、実際どう皌ぐか ― 仕組み・実䟋・始め方・サポヌト


Lily@bokuwalily― 個人開発者。Claude Code で自動化基盀を組みながら、iOSアプリやWebサヌビスを量産しおいたす

  • 制䜜物・蚘事は bokuwalily.com にたずめおいたす🖥
  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍

皆さんの ❀ やシェアが励みになりたす

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?