fix(halacha): authoritative rate-limit detection + early-morning catch-up window (supervisor) #251

Merged
chaim merged 1 commits from worktree-halacha-supervisor-ratelimit into main 2026-06-14 10:03:24 +00:00
Owner

הבעיה (ליל 13→14.6)

הדריינר רץ בחלון הלילה אך חילץ 0 הלכות. שורש-הכשל, שתי שכבות:

  1. מכסת-המנוי של claude.ai אזלה. חילוץ-ההלכות רץ על ה-Claude CLI (claude_session.py, total_cost_usd:0 = מנוי). כל מקטע קיבל api_error_status:429 — "You've hit your session limit · resets 2:30am (UTC)". ה-reset (2:30 UTC = 5:30 IDT) נחת מעט אחרי סגירת החלון (05:00 IDT) → כל החלון היה חסום.
  2. המתזמר סיווג שגוי כ-"hung". scan_rate_limit קורא רק 120 שורות-זנב מלוג-השגיאות; ה-429 (שורה 8,273/9,170) נקבר תחת ~900 שורות teardown שה-restart-storm של המתזמר עצמו ייצר → rate_limited:false כל הלילה → restart-hung כל 15 דק'. בנוסף "hold" לא עצר את הדריינר → המשך הלמת-429 ובזבוז המכסה שמחכים לה.

התיקון

Fix A — זיהוי rate-limit עמיד (durable):

  • quota_exhausted() חדש: מקור-האמת הוא endpoint-המכסה (subscription_usage, אותו util שה-UI של Claude Code מציג) — אינו תלוי בעומק-זנב-הלוג, ולכן 429 שגלל החוצה כבר לא "נעלם". scan_rate_limit נשאר רק כ-fallback כש-endpoint לא-זמין.
  • בזמן מוגבל עוצר דריינר online (hold-stopped) כדי לא להלום 429; מצית-מחדש כשהמכסה חוזרת (exit מיידי כש-endpoint <100%, או probe claude -p אם ה-endpoint למטה).

Fix B — חלון catch-up בוקר [05:00–07:00 IDT):

  • נפתח רק לניקוי backlog שנותר כשהמכסה חזרה (מגודר: not in_cooldown + תור≠ריק) — איפוס-5-שעות מאוחר נוחת לרוב מעט אחרי 05:00, וכך המכסה המשוחררת לא מתבזבזת עד הלילה הבא. הקצה המורחב (win=(23,7)) מועבר לדריינר כך ש-window-self-guard שלו מקבל את סבבי-ה-catch-up.

נתונים בטוחים: התיקים נשארו status='processing' for retry — שום הלכה לא אבדה.

בדיקות

  • 13 unit-tests עוברים: parse של ה-endpoint (5h/weekly/null/endpoint-down/multi-window), gating של ה-catch-up band (שעה×backlog×cooldown), הרחבת win.
  • py_compile נקי; status רץ מקצה-לקצה מול ה-endpoint החי (מכסה 5% כרגע).

Invariants

  • G1 (תיקון-במקור) — זיהוי rate-limit ממקור-המכסה הסמכותי, לא מתסמין-לוג שביר.
  • G2 (בלי מסלול מקביל) — שימוש חוזר באותו endpoint-מכסה ובאותו מנגנון-חלון שכבר קיימים בקובץ.
  • INV-G3 / X16 — לא נוגע ב-checkpointing הדטרמיניסטי per-chunk של הדריינר.
  • G12 — לא רלוונטי (סקריפט host-side pm2, אפס מגע-Paperclip).

🤖 Generated with Claude Code

## הבעיה (ליל 13→14.6) הדריינר **רץ** בחלון הלילה אך חילץ **0 הלכות**. שורש-הכשל, שתי שכבות: 1. **מכסת-המנוי של claude.ai אזלה.** חילוץ-ההלכות רץ על ה-Claude CLI (`claude_session.py`, `total_cost_usd:0` = מנוי). כל מקטע קיבל `api_error_status:429 — "You've hit your session limit · resets 2:30am (UTC)"`. ה-reset (2:30 UTC = 5:30 IDT) נחת **מעט אחרי** סגירת החלון (05:00 IDT) → כל החלון היה חסום. 2. **המתזמר סיווג שגוי כ-"hung".** `scan_rate_limit` קורא רק 120 שורות-זנב מלוג-השגיאות; ה-429 (שורה 8,273/9,170) נקבר תחת ~900 שורות teardown שה-restart-storm של המתזמר עצמו ייצר → `rate_limited:false` כל הלילה → `restart-hung` כל 15 דק'. בנוסף `"hold"` לא עצר את הדריינר → המשך הלמת-429 ובזבוז המכסה שמחכים לה. ## התיקון **Fix A — זיהוי rate-limit עמיד (durable):** - `quota_exhausted()` חדש: מקור-האמת הוא endpoint-המכסה (`subscription_usage`, אותו util שה-UI של Claude Code מציג) — **אינו תלוי בעומק-זנב-הלוג**, ולכן 429 שגלל החוצה כבר לא "נעלם". `scan_rate_limit` נשאר רק כ-fallback כש-endpoint לא-זמין. - בזמן מוגבל **עוצר דריינר online** (`hold-stopped`) כדי לא להלום 429; מצית-מחדש כשהמכסה חוזרת (exit מיידי כש-endpoint <100%, או probe `claude -p` אם ה-endpoint למטה). **Fix B — חלון catch-up בוקר `[05:00–07:00 IDT)`:** - נפתח **רק** לניקוי backlog שנותר כשהמכסה חזרה (מגודר: `not in_cooldown` + תור≠ריק) — איפוס-5-שעות מאוחר נוחת לרוב מעט אחרי 05:00, וכך המכסה המשוחררת לא מתבזבזת עד הלילה הבא. הקצה המורחב (`win=(23,7)`) מועבר לדריינר כך ש-window-self-guard שלו מקבל את סבבי-ה-catch-up. > **נתונים בטוחים:** התיקים נשארו `status='processing' for retry` — שום הלכה לא אבדה. ## בדיקות - **13 unit-tests** עוברים: parse של ה-endpoint (5h/weekly/null/endpoint-down/multi-window), gating של ה-catch-up band (שעה×backlog×cooldown), הרחבת `win`. - `py_compile` נקי; `status` רץ מקצה-לקצה מול ה-endpoint החי (מכסה 5% כרגע). ## Invariants - **G1** (תיקון-במקור) — זיהוי rate-limit ממקור-המכסה הסמכותי, לא מתסמין-לוג שביר. - **G2** (בלי מסלול מקביל) — שימוש חוזר באותו endpoint-מכסה ובאותו מנגנון-חלון שכבר קיימים בקובץ. - **INV-G3 / X16** — לא נוגע ב-checkpointing הדטרמיניסטי per-chunk של הדריינר. - **G12** — לא רלוונטי (סקריפט host-side pm2, אפס מגע-Paperclip). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
chaim added 1 commit 2026-06-14 10:03:18 +00:00
fix(halacha): authoritative rate-limit detection + early-morning catch-up window (supervisor)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
49efa94d60
הדריינר רץ בלילה 13→14.6 אך חילץ 0 הלכות: מכסת-המנוי של claude.ai אזלה
(`api_error_status:429 "You've hit your session limit · resets 2:30am UTC"`,
`total_cost_usd:0`), וה-reset (5:30 IDT) נחת מעט אחרי סגירת החלון (05:00 IDT).
המתזמר סיווג זאת שגוי כ-"hung" ועשה restart-storm כל 15 דק' — כי `scan_rate_limit`
קורא רק 120 שורות-זנב, וה-429 (שורה 8273/9170) נקבר תחת ~900 שורות teardown שה-churn
שלו עצמו ייצר. בנוסף "hold" לא עצר את הדריינר → המשך הלמת-429 ובזבוז המכסה.

Fix A — זיהוי rate-limit עמיד:
  • `quota_exhausted()` חדש: מקור-האמת הוא endpoint-המכסה (`subscription_usage`,
    אותו util שה-UI מציג) — durable, לא תלוי בעומק-זנב-הלוג. log-scrape רק כ-fallback.
  • בזמן מוגבל עוצר דריינר online (`hold-stopped`) כדי לא להלום 429; מצית-מחדש
    כשהמכסה חוזרת (exit מיידי כש-endpoint <100%, או probe `claude -p` אם endpoint למטה).

Fix B — חלון catch-up בוקר [05:00–07:00 IDT):
  • נפתח רק לניקוי backlog שנותר כשהמכסה חזרה (מגודר: לא-מוגבל + תור≠ריק) כדי
    שהמכסה המשוחררת לא תתבזבז עד הלילה הבא. הקצה המורחב מועבר לדריינר (window self-guard).

נתונים בטוחים — תיקים נשארו 'processing' for retry, שום הלכה לא אבדה.
13 unit-tests עוברים (parse endpoint, gating של catch-band, win extension); `status` חי OK.

Invariants: מקיים G1 (תיקון-במקור: זיהוי ממקור-מכסה סמכותי, לא מתסמין-לוג),
G2 (אותו endpoint+מנגנון-חלון קיימים — בלי מסלול מקביל), INV-G3/X16 (לא נוגע
ב-checkpointing הדטרמיניסטי). G12 לא רלוונטי (host-side pm2, בלי Paperclip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
chaim merged commit 83293ca619 into main 2026-06-14 10:03:24 +00:00
chaim deleted branch worktree-halacha-supervisor-ratelimit 2026-06-14 10:03:24 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ezer-mishpati/legal-ai#251