🎯 サヌキットが開いおいる間に、1本だけ本物のリク゚ストを通す ― 優先プロヌブずSKIPPED蚘録の萜ずし穎 — リヌダヌ×
🎯

サヌキットが開いおいる間に、1本だけ本物のリク゚ストを通す ― 優先プロヌブずSKIPPED蚘録の萜ずし穎

#claudecode#automation#launchd#python2026-09-21 · 箄11分

前回、ゲヌトが止めるのは1行であっおバッチ党䜓ではない話を曞きたした。今回は同じclaude-quota-guard.pyの䞭でも、サヌキットが開いたたた埩旧を確かめるclaim_priority_probeずrun_jobの実装です。「クォヌタ埩旧を怜知したら党郚再実行する」quota-catchup.py偎の話をする前段ずしお、そのcatchupが読みにいくSKIPPEDマヌカヌがどう䜜られおいるかを先に芋おおきたす。

困りごずサヌキットは党ゞョブを䞀埋に止めるが、投皿系は埅おない

claude-quota-guard.pyはクォヌタ䞊限を怜知するずopen_untilたでEXIT_CIRCUIT_OPEN75を返し、ガヌド察象の15本すべおを䞀埋に止めたす。コヌド䞭のコメントに、なぜこれだけでは足りなかったかが曞いおありたす。

# 🔎 2026-08-21: circuit が開くず党ゞョブが䞀埋で止たるため、消費の倧半を占める
# 返信/゚ンゲヌゞ系がクォヌタを䜿い切った巻き添えで「投皿」たで停止しおいた。
# 実枬(launchd.log 环蚈): xpilot.autopost は 231実行/308スキップ実行率43%で、
# threadspilot.engage(64%) より優先床が䜎い扱いになっおいた。投皿はその時間垯を逃すず
# 二床ず埋たらないので、--priority を付けたゞョブだけは circuit が開いおいおも
# この間隔で1回だけ詊行を蚱す。詊行が通ればクォヌタ回埩の早期怜知にもなる
# (埓来は open_until たで盲目的に埅぀だけだった)。

claude-quota-guard.py:18-24

䞀埋に止めるず、消費量の倧きい゚ンゲヌゞ系に巻き添えを食っお投皿系(xpilot.autopost)が実行率43%たで萜ちおいた、ずいう実枬です。しかもopen_untilは「䞊限文からパヌスしたreset時刻」か「6時間cooldown」のどちらかで決たるrecord_claude_resultので、実際のクォヌタがそれより早く戻っおいおも、サヌキットは埋儀にその時刻たで開いたたたです。投皿のように「その時間垯を逃すず二床ず埋たらない」ゞョブにずっお、これは無芖できたせん。

蚭蚈--priorityゞョブだけに30分1回の詊行暩を枡す

察策は「サヌキットが開いおいおも、優先ゞョブにだけ䞀定間隔で1回、本物のリク゚ストを通す」こずです。呌び出し偎はrun_jobにコマンドを枡すずきに--priorityを付けたす。

def run_job(label: str, command: list[str], priority: bool = False) -> int:
    if not command:
        print("claude quota guard: --job requires a command after --", file=sys.stderr)
        return 2
    status = circuit_status()
    probe_claimed = False
    if status["is_open"]:
        if not (priority and claim_priority_probe()):
            print(
                "CLAUDE_QUOTA_JOB_SKIPPED "
                f"job={label} reason={status['reason']} remaining={status['remaining_seconds']}s ts={now()}",
                file=sys.stderr,
            )
            return 0
        probe_claimed = True
        print(
            "CLAUDE_QUOTA_JOB_PRIORITY_PROBE "
            f"job={label} reason={status['reason']} remaining={status['remaining_seconds']}s ts={now()}",
            file=sys.stderr,
        )

claude-quota-guard.py:471-490

priorityが立っおいおも、実際に詊行できるかどうかはclaim_priority_probe()が握っおいたす。

def claim_priority_probe() -> bool:
    """circuit が開いおいる間、優先ゞョブに詊行暩を1぀枡す。

    間隔は党優先ゞョブで共有する(=1本が䜿ったら次の枠たで他も埅぀)。䞊限に本圓に
    達しおいる間に䜕本も叩いおもクォヌタは戻らないため、叩く回数自䜓を絞る。
    """
    if PRIORITY_PROBE_INTERVAL <= 0:
        return False
    with locked_state() as state:
        last = int(state.get("last_priority_probe") or 0)
        current = now()
        if current - last < PRIORITY_PROBE_INTERVAL:
            return False
        state["last_priority_probe"] = current
        return True

claude-quota-guard.py:454-468、PRIORITY_PROBE_INTERVALはclaude-quota-guard.py:25で既定1800秒

last_priority_probeはlocked_state()fcntl.flockで排他するJSON氞続状態に茉っおいる1個のタむムスタンプです。ゞョブ単䜍ではなくサヌキット単䜍でこの倀を持぀のがポむントで、「誰が詊行したか」は問わず「最埌に誰かが詊行しおから30分経ったか」だけを芋たす。

詊行暩を埗た偎は、subprocess.runする子プロセスの環境倉数にCLAUDE_QUOTA_PRIORITY_PROBE=1を枡したす。

    guard = str(Path(__file__).resolve())
    env = os.environ.copy()
    env["CLAUDE_AUTOMATION_GUARD"] = "1"
    env["CLAUDE"] = guard
    env["CLAUDE_BIN"] = guard
    if probe_claimed:
        # 内偎の run_claude に「プロヌブずしお走っおいる」こずを䌝える(これが無いず circuit で即 75 になる)
        env["CLAUDE_QUOTA_PRIORITY_PROBE"] = "1"

claude-quota-guard.py:491-498

このフラグを受け取る偎がrun_claudeです。ゞョブコマンドの内郚でclaude本䜓を叩くずき、guard自身を経由するようにPATHが差し替えられおいるので、子プロセスの䞭でもう䞀段circuit_status()のチェックが走りたす。

def run_claude(arguments: list[str]) -> int:
    status = circuit_status()
    if status["is_open"]:
        # run_job が優先プロヌブを claim した子プロセスだけは circuit を玠通りしお実 claude を叩く。
        # 結果は record_claude_result に入るので、䞊限文なら circuit が延び、成功なら閉じる。
        if os.environ.get("CLAUDE_QUOTA_PRIORITY_PROBE") != "1":
            print(
                "CLAUDE_QUOTA_CIRCUIT_OPEN "
                f"reason={status['reason']} remaining={status['remaining_seconds']}s",
                file=sys.stderr,
            )
            return EXIT_CIRCUIT_OPEN
        print(
            "CLAUDE_QUOTA_PRIORITY_PROBE_PASS "
            f"reason={status['reason']} remaining={status['remaining_seconds']}s",
            file=sys.stderr,
        )

claude-quota-guard.py:416-432

環境倉数を䌝播させ忘れるず、run_job偎で詊行暩を埗おも、内偎のrun_claudeがもう䞀床circuitを芋お即EXIT_CIRCUIT_OPENを返すだけになりたす。二段構えのゲヌトを䞡方通すための1行がenv["CLAUDE_QUOTA_PRIORITY_PROBE"] = "1"です。

そしお実際に叩いた結果は、い぀も通りrecord_claude_resultに入りたす。䞊限文がただ返っおくればopen_untilが延び、成功すればcircuitが閉じたす。詊行が倱敗しおも成功しおも、その1回の結果がそのたたサヌキットの次の状態を決めるずいう意味で、これは「1本だけ通す本物のリク゚スト」です。

螏んだ萜ずし穎130分1本は「党優先ゞョブの共有カりンタ」

claim_priority_probeのdocstringにある通り、PRIORITY_PROBE_INTERVALはゞョブ単䜍ではなく党䜓で共有です。--priorityが付いたゞョブが同じ30分の枠に耇数スケゞュヌルされおいおも、最初の1本がclaim_priority_probe()を通した瞬間にlast_priority_probeが曎新されるので、埌続はpriority and claim_priority_probe()の時点でFalseになり、実クラむアントを1回も叩かずにSKIPPEDぞ萜ちたす。

これは狙った挙動です。䞊限に本圓に達しおいる間は䜕本叩いおもクォヌタは戻らないので、詊行回数自䜓を絞る蚭蚈になっおいたす。ただし運甚䞊は「優先ゞョブが耇数あるからずいっお、それぞれが独立に30分おきの機䌚を持぀わけではない」こずを忘れるず、「なぜこのゞョブだけ埩旧確認の機䌚が回っおこないんだろう」で1本ハマりたす。

螏んだ萜ずし穎2プロヌブが空振りするず、黙っおRAN exit≠0ずしお残る

これが今回の䞻題です。run_jobはsubprocess.runの埌、プロヌブが空振りしたかどうかを芋おマヌカヌを出し分けたす。

    try:
        result = subprocess.run(command, env=env, check=False)
    except OSError as exc:
        print(f"claude quota guard job={label}: {exc}", file=sys.stderr)
        return 127
    if probe_claimed and result.returncode != 0 and circuit_status()["is_open"]:
        # 優先プロヌブが䞊限のたた空振りした。RAN exit≠0 のたた残すず quota-catchup.py
        # 最新マヌカヌ=SKIPPED だけを再実行から挏れ、埩垰埌も圓日分が欠番になる
        # 2026-09-13〜16 の note2-daily / codex-note-funnel 実枬。SKIPPED ずしお蚘録する。
        print(
            "CLAUDE_QUOTA_JOB_SKIPPED "
            f"job={label} reason=priority-probe-quota exit={result.returncode} ts={now()}",
            file=sys.stderr,
        )
        return result.returncode
    print(
        f"CLAUDE_QUOTA_JOB_RAN job={label} exit={result.returncode} ts={now()}",
        file=sys.stderr,
    )
    return result.returncode

claude-quota-guard.py:499-518

この分岐を入れる前は、プロヌブが詊行暩を埗お実際に子プロセスを起動した時点で「詊行した」ずいう事実だけを芋お、末尟のCLAUDE_QUOTA_JOB_RANを玠通りで出しおいたした。プロヌブがたた䞊限に圓たっおexit≠0で終わっおも、ログに残るのはCLAUDE_QUOTA_JOB_RAN job=... exit=1です。

問題は、このRANマヌカヌがquota-catchup.pyにずっお「もう枈んだ扱い」になるこずです。quota-catchup.pyが再実行候補を絞るlatest_job_markerはこう曞かれおいたす。

def latest_job_marker(paths: list[Path], label: str) -> Optional[Tuple[str, int]]:
    """Return the newest timestamped skip/run marker for one launchd label."""
    marker_pattern = re.compile(
        r"CLAUDE_QUOTA_JOB_(SKIPPED|RAN)\s+job="
        + re.escape(label)
        + r"(?=\s|$).*\bts=(\d+)(?=\s|$)"
    )
    ...

def latest_marker_is_today_skip(paths: list[Path], label: str, today: datetime.date) -> bool:
    marker = latest_job_marker(paths, label)
    if marker is None:
        return False
    marker_type, timestamp = marker
    return marker_type == "SKIPPED" and datetime.fromtimestamp(timestamp).astimezone().date() == today

quota-catchup.py:160-193

芋おの通り、この刀定はマヌカヌの皮類SKIPPEDかRANかしか芋おおらず、RAN偎のexitコヌドは䞀切チェックしおいたせん。プロヌブが空振りしおexit=1で終わっおも、最埌に出たログがCLAUDE_QUOTA_JOB_RANである限りlatest_marker_is_today_skipはFalseを返し、find_candidatesの再実行察象から静かに倖れたす。

その結果、サヌキットが本圓に閉じた埌も、このゞョブは「今日はもうRAN枈み」ずいう誀った蚘録のたた、次のスケゞュヌル時刻翌朝などたで攟眮されたす。実際にnote2-dailyずcodex-note-funnelが2026-09-13〜16の間、この経路で圓日分が欠番になったずコメントに実枬が残っおいたす。

盎し方はシンプルで、「プロヌブがcircuit open䞭に空振りした」ずいう条件probe_claimed and result.returncode != 0 and circuit_status()["is_open"]を先に刀定し、その堎合だけCLAUDE_QUOTA_JOB_RANではなくCLAUDE_QUOTA_JOB_SKIPPED reason=priority-probe-quotaを出したす。関数の戻り倀自䜓はresult.returncodeのたた倉えおいたせん。launchd偎のLastExitStatusには実際の倱敗が正しく蚘録され぀぀、quota-catchup.pyが芋るログマヌカヌだけを「ただ再詊行すべきもの」に付け替える、ずいう切り分けです。

exit codeず再実行すべきかどうかは別の軞です。今回のバグは、この2぀を「RANずいう1぀のマヌカヌ」に抌し蟌めおいたために、quota-catchup.py偎がexit≠0のケヌスを想定しおいなかったこずで起きたした。ログマヌカヌを蚭蚈するずきは、「実際に䜕が起きたか」ず「埌続バッチが次に䜕をすべきか」を同じ文字列に盞乗りさせないほうが安党です。

たずめ

  • サヌキットは䞀埋に止めるが、投皿系のように機䌚を逃すず取り返せないゞョブは--priorityで30分に1回だけ詊行暩を持おる
  • 詊行暩はclaim_priority_probeがlocked_stateのlast_priority_probeで管理し、ゞョブ単䜍ではなく党優先ゞョブで共有する。耇数の優先ゞョブが同じ枠に重なっおも機䌚は1回
  • 詊行暩を埗た子プロセスにはCLAUDE_QUOTA_PRIORITY_PROBE=1を枡す必芁がある。枡し忘れるず内偎のrun_claudeが二段目のcircuitチェックで即座に匟く
  • プロヌブが空振りしたらCLAUDE_QUOTA_JOB_RAN exit≠0ではなくCLAUDE_QUOTA_JOB_SKIPPED reason=priority-probe-quotaずしお蚘録する。quota-catchup.pyの再実行刀定はマヌカヌの皮類しか芋おおらず、exitコヌドは芋おいないため
  • ログマヌカヌは「実際に䜕が起きたか」ず「埌続バッチが䜕をすべきか」を分けお蚭蚈する

次回は、このSKIPPEDマヌカヌをquota-catchup.pyがどう拟っお埩旧埌にどれだけ再実行するか決めおいるかを曞きたす。


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

  • AIで「寝おおも回る仕組み」を䜜っお月120䞇にした話は noteの有料蚘事 に💰
  • OSS: github.com/bokuwalily 🐙
  • 最新情報・お問い合わせは X @bokuwalily ぞ🌍
  • AI導入・自動化の盞談ず実装テンプレ7本の配垃は 公匏LINE から💬

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