feat(loop-automation): handle session-limit reset + 529 Overloaded banners#24
Merged
Merged
Conversation
…nners
Extends the agentic-loop supervisor's banner detection with two more stall
reasons so the "Keep going" loop recovers from them automatically:
- "You've hit your session limit · resets 3:10pm (UTC)" is now classified as a
usage-/session-window limit. The reset time is parsed timezone-aware
("(UTC)", "(GMT)", "Z", or an explicit +/-HH[:MM] offset) and converted to
UTC, and the wake fires 5 minutes after the stated reset so the window has
actually rolled (was a 60s buffer).
- "API Error: 529 Overloaded" is a new transient LimitSignal::Overloaded with a
4-minute backoff (within the provider's suggested 3-5 min), retried until it
clears. Backed by a new WakeupKind::OverloadRetry (plain TEXT column, no
migration; renders as a short retry in the UI).
The reset-time parser now returns the raw UTC instant and the scheduler owns
the post-reset buffer, so usage and session windows can differ cleanly. Adds
unit tests for the session-limit banner, 529 detection, and offset conversion
(UTC-5, UTC+05:30, bare offsets). Regenerated shared types.
Applies the consensus findings from a multi-agent review (Codex + council + code-review xhigh) of the session-limit / 529 additions: - Reset times with no explicit zone are now read in the host's local timezone (converted to UTC), not assumed-UTC — CLI agents render reset clocks in local time. Explicit (UTC)/(GMT)/Z/±HH[:MM] still win. `parse_reset_at` takes an injectable default_tz so this is deterministically testable. - Delivery now checks the *same* limit kind is still visible (not just "some limit"), so an OverloadRetry can't fire into a now-different usage/rate cap and burn an attempt. Adds `LimitSignal::wakeup_kind()`. - Detection order is rate-limit -> usage/session -> 529, so a stale "overloaded" line above a live usage banner no longer masks the window's reset (which would hammer a hard-capped agent every 4 min). - 529 detection is line-scoped (529 and "overloaded" on the same line) to avoid false positives from incidental pane text. - Day-roll fix: `parse_reset_at` returns today's clock instant; the new `usage_wake_at` adds the 5-min buffer and only rolls to tomorrow when the buffered wake has already elapsed — a reset seen minutes late wakes soon instead of ~24h later (matters across the frequent fork restarts). - `advance_past_ampm` consumes the trailing dot of "3:10 p.m." so a following "(UTC-5)" isn't dropped. - New `WakeupKind::Unknown`: unknown persisted kinds parse to it instead of `Manual`, so a rolled-back/foreign row is skipped by the kind-match gate rather than delivered unconditionally. - Collapses the three near-identical scheduler arms into one shared insert. Not changed (with reason): the migration comment listing kinds is left stale — editing an already-applied migration changes its SQLx checksum and breaks validation on existing DBs. The fixed 4-min overload backoff is intentional per the desired 3-5 min retry; the attempt cap bounds any sustained-outage hammering. Adds tests for local-tz default, dotted-meridiem offset, usage_wake_at buffer/ roll boundary, signal->kind mapping, usage-wins-over-stale-529, line-scoped 529, and unknown-kind parsing. Regenerated shared types.
- Leave a due wake-up pending (retry next tick) when pane capture fails transiently, instead of marking it fired and losing the retry — the session still exists (checked earlier), so a capture blip must not drop the wake-up. - parse_tz_offset_secs no longer collapses a malformed UTC/GMT offset (e.g. "UTC+99", "UTCx") to plain UTC; it returns None so the caller falls back to its default zone. Adds tests for both malformed forms.
Owner
Author
✅ ship-it complete — ready for review, CI green, CodeRabbit cleanGate: Review battery run: Codex review · 4-persona council (mixed) · Applied from review (consensus):
Rejected with reason: editing the applied migration's comment (SQLx checksum), greedy-digit / trailing-signed-offset parses (inputs Claude's banner grammar doesn't emit), fixed 4-min overload backoff (intentional per the 3–5 min ask). Backend tests 8 → 30; clippy clean; frontend typechecks; shared types regenerated. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two more stall reasons the agentic-loop ("Keep going") supervisor now detects and auto-recovers from, extending the banner classifier from #23.
1. Session-limit reset banner
You've hit your session limit · resets 3:10pm (UTC)UsageLimited).(UTC)/(GMT)/Z/±HH[:MM]are honored; when no zone is stated the time is read in the host's local zone (CLI agents render reset clocks in local time — the supervisor runs on the same host) and converted to UTC. A malformed offset (UTC+99) falls back to the default zone rather than silently becoming UTC.usage_wake_at), and only rolls to the next day when that buffered wake has already elapsed — so a reset seen a few minutes late wakes soon instead of ~24h later.2.
529 OverloadedAPI Error: 529 Overloaded …LimitSignal::Overloadedwith a 4-minute backoff (your "3–5 min"), retried until it clears. Backed by a newWakeupKind::OverloadRetry(plainTEXTcolumn — no migration).529andoverloadedon the same line) to avoid false positives from incidental pane text.Correctness guarantees (added under review)
Unknownkind (rolled-back/foreign row) is skipped rather than delivered unconditionally.Not changed (deliberately)
kindcomment is left stale on purpose — editing an already-applied migration changes its SQLx checksum and breaks validation on existing DBs.Review & tests
Ran through a multi-agent review battery: Codex review, a 4-persona council,
/code-review xhigh(18 agents), 4/simplifyagents, and CodeRabbit. Findings triaged, real ones applied, false positives disproven with regression tests (UTF-8 boundary safety,+0530HHMM offset).Backend unit tests 8 → 30 across the two crates (timezone/local-default conversion, dotted-meridiem offset,
usage_wake_atbuffer/roll boundary, signal→kind mapping, usage-wins-over-stale-529, line-scoped 529, unknown-kind parse).cargo clippyclean, frontend typechecks, shared types regenerated (WakeupKindgainsoverload_retry+unknown).Deploy note
Fork build at
/optneeds a rebuild + restart to pick this up.🤖 Generated with Claude Code