Resume spend-limit and usage-credit blocks, retried hourly
This commit is contained in:
+41
-12
@@ -27,11 +27,17 @@ DEFAULT_PROMPT = (
|
||||
"transient failure (usage limit, API outage, or network), which has cleared."
|
||||
)
|
||||
|
||||
RECOVERABLE = re.compile(
|
||||
r"hit your (session|weekly|opus) limit"
|
||||
r"|usage limit"
|
||||
r"|rate.?limit"
|
||||
r"|api error"
|
||||
# Every quota Claude Code can report as "You've hit your <limit> · <how to clear
|
||||
# it>", including the ones that cost money rather than time.
|
||||
QUOTA_BLOCK = re.compile(
|
||||
r"(session|weekly|opus|sonnet|fable \d+|fast|spend|rate.?|usage( credit)?) ?limit"
|
||||
r"|out of (usage|extra usage)"
|
||||
r"|usage credits",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
TRANSIENT_BLOCK = re.compile(
|
||||
r"api (error|unavailable)"
|
||||
r"|overloaded"
|
||||
r"|connection (error|reset|closed|failure)"
|
||||
r"|network error"
|
||||
@@ -41,18 +47,29 @@ RECOVERABLE = re.compile(
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
# A session parked on a human decision must never be auto-resumed. Vetoes even
|
||||
# when the same detail also names a limit.
|
||||
# A session parked on a human decision must never be auto-resumed, and an
|
||||
# entitlement someone else controls never clears by waiting. Vetoes even when
|
||||
# the same detail also names a limit.
|
||||
NEEDS_HUMAN = re.compile(
|
||||
r"input needed|waiting for|permission|approval"
|
||||
r"|awaiting (your )?(direction|decision|input|answer|go.?ahead)"
|
||||
r"|needs? (your )?(direction|decision|input)",
|
||||
r"|needs? (your )?(direction|decision|input)"
|
||||
r"|seat type does ?n.t include"
|
||||
r"|disabled by your admin"
|
||||
r"|disabled for your org"
|
||||
r"|limit is set to \$0",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
|
||||
LIMIT_BLOCK = re.compile(r"(session|weekly|opus|usage|rate.?) ?limit", re.IGNORECASE)
|
||||
# Money, not time: cleared by raising the cap, by the month rolling over, or by
|
||||
# the subscription window putting the session back on included quota. None of
|
||||
# those announce themselves, so poll rather than wait out a window.
|
||||
SPEND_BLOCK = re.compile(
|
||||
r"spend limit|usage credit|out of (usage|extra usage)|usage credits", re.IGNORECASE
|
||||
)
|
||||
WEEKLY_LIMIT = re.compile(r"weekly limit", re.IGNORECASE)
|
||||
SESSION_WINDOW = timedelta(hours=5)
|
||||
SPEND_WINDOW = timedelta(hours=1)
|
||||
WEEKLY_WINDOW = timedelta(days=7)
|
||||
|
||||
RESETS_AT = re.compile(
|
||||
@@ -160,6 +177,13 @@ def parse_reset(detail, blocked_at):
|
||||
return target
|
||||
|
||||
|
||||
def resumable(detail):
|
||||
"""True when `detail` names a cause that clears without anyone acting."""
|
||||
if NEEDS_HUMAN.search(detail):
|
||||
return False
|
||||
return bool(QUOTA_BLOCK.search(detail) or TRANSIENT_BLOCK.search(detail))
|
||||
|
||||
|
||||
def reset_moment(detail, blocked_at):
|
||||
"""When the block should have lifted, or None if it can be retried at once.
|
||||
|
||||
@@ -167,9 +191,14 @@ def reset_moment(detail, blocked_at):
|
||||
rewritten after the reset already happened, which would otherwise push the
|
||||
parsed time-of-day a full day into the future.
|
||||
"""
|
||||
if not LIMIT_BLOCK.search(detail):
|
||||
if not QUOTA_BLOCK.search(detail):
|
||||
return None
|
||||
window = WEEKLY_WINDOW if WEEKLY_LIMIT.search(detail) else SESSION_WINDOW
|
||||
if WEEKLY_LIMIT.search(detail):
|
||||
window = WEEKLY_WINDOW
|
||||
elif SPEND_BLOCK.search(detail):
|
||||
window = SPEND_WINDOW
|
||||
else:
|
||||
window = SESSION_WINDOW
|
||||
stated = parse_reset(detail, blocked_at)
|
||||
bound = blocked_at + window
|
||||
return min(stated, bound) if stated else bound
|
||||
@@ -209,7 +238,7 @@ def candidates(sessions, jobs_dir, now):
|
||||
continue
|
||||
|
||||
detail = str(state.get("detail") or "")
|
||||
if NEEDS_HUMAN.search(detail) or not RECOVERABLE.search(detail):
|
||||
if not resumable(detail):
|
||||
continue
|
||||
|
||||
blocked_at = datetime.fromtimestamp(state_file.stat().st_mtime).astimezone()
|
||||
|
||||
Reference in New Issue
Block a user