ð 鱿¬¡ã¯ã©ãŒã¿äžéã§ãžã§ããå šæ» ããåã«ãµãŒããããã¬ãŒã«ãŒã匵ã
æå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ãµãŒãã¹ãéç£ããŠããŸã
- AIã§ãå¯ãŠãŠãåãä»çµã¿ããäœã£ãŠæ120äžã«ãã話㯠noteã®ææèšäº ã«ð°
- OSS: github.com/bokuwalily ð
- ææ°æ å ±ã»ãåãåãã㯠X @bokuwalily ãžð
çããã® â€ïž ãã·ã§ã¢ãå±ã¿ã«ãªããŸãïŒ