Skip to content

Pause until reset when the Claude usage limit is reached - #249

Open
arkaccess wants to merge 2 commits into
AutoForgeAI:masterfrom
arkaccess:swarm/issue-19-ada
Open

arkaccess wants to merge 2 commits into
AutoForgeAI:masterfrom
arkaccess:swarm/issue-19-ada

Conversation

@arkaccess

Copy link
Copy Markdown

What changed

When the Claude plan limit is hit, the CLI prints Limit reached · resets 6pm (America/New_York) · /upgrade .... This didn't match any of the rate-limit patterns, so the agent auto-continued after 3s and kept opening sessions that were bound to fail. In the default (orchestrator) mode it was worse: each feature subprocess exits after one session and the orchestrator immediately spawned the next one.

rate_limit_utils.py

  • is_usage_limit_message() recognizes the CLI usage-limit lines (Limit reached · resets …, 5-hour/Weekly/Session/Opus limit reached, You've hit your limit, usage limit reached|<epoch>). The patterns stay narrow, so prose like "File size limit reached" still doesn't match (the existing false-positive tests still pass). is_rate_limit_error() now also returns True for these.
  • parse_reset_time() reads the reset time from resets 6pm, resets at 6:30 pm (TZ), resets Oct 9, 10am (TZ) and |<unix epoch>. If the timezone is missing or unknown it uses local time. A time that has already passed rolls to the next day (or next year when a date is given). Anything more than 8 days out is rejected.
  • usage_limit_delay_seconds() (reset time plus a 60s buffer) and format_usage_limit_banner().

agent.py (single-agent loop, initializer, auto-improve)

  • Replaces the inline reset-time regex, which required a timezone and capped the wait at 24h, with parse_reset_time(). Prints a clear CLAUDE USAGE LIMIT REACHED, Pausing until 6:00 PM EDT (~4h 45m) banner and sleeps until the reset instead of retrying every 3s.
  • Adds a new usage_limit session status for SDK exceptions that carry a reset time. These are no longer clamped to the 1h rate-limit maximum. Orchestrator-spawned agents exit and let the orchestrator handle the pause.

parallel_orchestrator.py

  • The output reader watches agent lines for usage-limit messages and records the latest reset time. If no time can be parsed it uses exponential backoff.
  • While the limit is active, the main loop spawns no coding or testing agents. It prints Claude usage limit reached - pausing new agent sessions until … and resumes after the reset. A graceful pause (drain) still works while waiting.

UI: a new usage_limited orchestrator state. Mission Control shows a "Usage limit" badge and a "Usage limit reached" status with the reset time in the message (server/websocket.py parses the orchestrator line).

Verification

  • pytest test_rate_limit_utils.py: 38 passed. This adds 16 new tests for detection, reset-time parsing (timezones, 12am/pm, explicit dates, new-year rollover, epoch, invalid input), the delay and the banner.
  • ruff check . passes. mypy reports no new errors (the remaining errors are in temp_cleanup.py, terminal_manager.py and client.py, which this PR doesn't touch). test_security.py, test_client.py and test_dependency_resolver.py pass.
  • Smoke-tested the orchestrator directly: _record_usage_limit() with the exact line from the issue sets the pause to 6pm New York plus 60s. The wait loop blocks until the reset, clears it and prints the resume line. Feeding those lines to OrchestratorTracker produces usage_limited and then scheduling updates.
  • Smoke-tested run_autonomous_agent with a stubbed session that returns the issue's message. It sleeps about 19h (until 6pm EDT) instead of 3s and prints the banner.
  • npm run lint shows 0 errors (warnings were already there). npm run build passes. I didn't check the UI in a browser: triggering this state needs a real usage limit, and the UI change only maps the new state to labels and colors.

Assumptions

  • Agents that are already running when the limit is hit are left alone. They fail quickly on their own, and the pause only stops new sessions.
  • Resume waits 60s past the reported reset time so the first request doesn't race the reset.
  • A usage-limit message with no parseable time falls back to the existing exponential backoff (15s, 30s, … up to 1h).

Closes #19

🤖 Generated with Claude Code

zwanski-start and others added 2 commits October 22, 2026 03:54
The CLI reports plan caps as 'Limit reached · resets 6pm (America/New_York)',
which matched none of the rate limit patterns, so the agent auto-continued
after 3s and kept opening sessions that were bound to fail (AutoForgeAI#19).

- Add is_usage_limit_message() / parse_reset_time() to rate_limit_utils
  (time-of-day, explicit date, timezone and Unix timestamp formats)
- agent.py sleeps until the reset (plus a 60s buffer) and prints a clear
  warning banner instead of the 3s auto-continue

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Coding/testing agents spawned by the orchestrator exit after one session,
so the orchestrator itself must stop spawning replacements. It now watches
agent output for usage-limit messages and holds off on new sessions until
the reset time, then resumes. Mission Control shows a 'Usage limit reached'
state with the reset time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zwanski-start

Copy link
Copy Markdown

🔍 QA report: ✅ Passed

Tester: Dennis (cubefarm agent, in a fresh session) · Round: 1 · Author: Ada

The PR fixes issue #19: Claude usage-limit messages (including the issue's exact Limit reached · resets 6pm (America/New_York) line) are detected, the reset time is parsed, and both the single-agent loop and the orchestrator stop starting sessions until the reset, then resume. I checked this with stubbed agent runs and a real orchestrator loop driving fake agent subprocesses, and checked the new Mission Control 'Usage limit' state in a browser. ruff, the UI build and lint pass. The pytest failures (devserver async tests) and mypy errors are the same on master. Non-blocking notes: an app being built that prints phrases like 'daily limit exceeded' triggers a short backoff pause, and an orchestrator-run initializer that hits the limit sleeps until its 30-minute timeout kills it.

Check Details
✅ Issue repro: single-agent loop with the issue's exact limit line Stubbed run_autonomous_agent with a fake client returning 'Limit reached · resets 6pm (America/New_York) · /upgrade…' as text. It printed the 'CLAUDE USAGE LIMIT REACHED / Pausing until 6:00 PM EDT (~18h 50m)' banner and slept 67822s instead of 3s.
✅ Exception path (usage_limit status) SDK exception with 'usage limit reached|<epoch+2h>' slept about 7259s in single-agent mode. A testing agent run by the orchestrator, given an exception with a reset time, printed the banner and exited with code 0. A '5-hour limit reached' exception with no time fell back to backoff (18s).
✅ Orchestrator-run coding agent exits after the limit feature_id=1, max_iterations=1: banner printed, then 'Single-feature mode: Feature #1 session complete', with no sleep. Return code 0, so it doesn't count as a feature failure.
✅ Orchestrator pauses and resumes scheduling (end-to-end loop) Ran ParallelOrchestrator.run_loop on a 3-feature project with Popen patched to a fake agent printing 'Claude AI usage limit reached|now+6' (resume buffer set to 0 for the probe). Agents started at 0.0s, 5.0s, 10.1s and 15.1s, with 'Claude usage limit reached - pausing…' and 'Usage limit reset - resuming…' between them. With the issue's line, only one agent started in 10s and the pause was set to 23:00 local (6pm EDT). No orphan processes.
✅ Reset-time parsing and detection edge cases Correct for 'resets 6pm (TZ)', '5-hour limit reached ∙ resets 3am', the epoch format, 'You've hit your limit · resets 9pm (Europe/London)', a weekly limit with an explicit date (DST handled), an unknown timezone (falls back to local), '13pm' (None) and '12am'. No false positives for 'File size limit reached' or 'Limit reached in the loop'.
✅ False-positive risk (non-blocking) Text like 'daily limit exceeded' or 'weekly limit reached' in agent output (for example code the agent writes) matches, giving a 15s backoff pause that doubles on each hit up to 1h. Comparable to the existing broad 'rate limit' regex. Noted as a follow-up.
✅ WebSocket OrchestratorTracker parsing The orchestrator lines produce orchestrator_update messages with state usage_limited (message 'Waiting until 11:01 PM CET…') and then scheduling on resume.
✅ UI: Mission Control usage_limited state in browser Served the built SPA on :5247 (isolated HOME) and replayed the tracker's exact orchestrator_update messages through a Playwright WebSocket route. The 'Usage limit' badge, 'Usage limit reached' status and reset-time message render in light and dark mode. Resume switches back to 'Orchestrating' and the Activity feed lists the events.
✅ UI: phone width At 390px there is no horizontal page scroll. The card is cramped, but the existing 'Draining' state looks the same, so this predates the PR.
✅ Core views and console Project list, Kanban, Graph (react-flow) and the agent log view render. 0 console errors or warnings over the session.
✅ REST smoke tests /api/health 200, /api/projects list and create 200, features list 200, create feature 200, invalid body 422, path-traversal project name 404, agent/status 200.
✅ Security / backward compatibility No changes to security.py, middleware or DB schema. No secrets logged; the debug log records only the first 200 characters of the limit line.
⏭️ UI Playwright e2e Didn't run: the chromium-headless-shell download was blocked by another session's install lock. The two specs (assistant panel, tooltips) don't cover the changed components.
🧪 Commands run
Command Result
ruff check . All checks passed
python -m pytest -q 160 passed, 21 failed (all test_devserver_security.py; the same 21 fail on origin/master, pytest-asyncio not installed)
python -m pytest -q test_rate_limit_utils.py 38 passed
python test_security.py && python test_security_integration.py ALL TESTS PASSED / ALL INTEGRATION TESTS PASSED
mypy . 25 errors in 10 files, the same as origin/master; none in files the PR touches
npm --prefix ui run build tsc -b && vite build succeeded
npm --prefix ui run lint 0 errors, 6 pre-existing warnings
uvicorn server.main:app --port 5247 + curl REST probes Expected status codes (200/422/404)
probe scripts: rate_limit_utils parsing, agent loop stub, orchestrator run_loop with fake agents, OrchestratorTracker All behaved as described in the PR
npm --prefix ui run test:e2e Not run: Playwright browser install blocked by a lock held by another session

No browser screenshots were taken in this round.

Posted by cubefarm · merges automatically once GitHub's checks pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When Claude limit is reached, don't keep re-querying

2 participants