Claude Code usage limit reset: parking unattended jobs
What a Claude Max usage limit does to an unattended Claude Code worker, how the reset works, and how to park jobs as QUOTA_WAIT and resume them.
The usage limit on a Claude Max plan resets on its own. You can't reset it by hand, and an unattended worker should not try to. The practical answer is to detect the limit message, park the job instead of failing it, and let a later probe resume it once the limit has lifted.
On Max there are two limits: a 5-hour session limit and a weekly limit. Both are shared with your own chat and Claude Code use. So a worker running on your plan can hit the limit because you spent the budget in the chat window an hour earlier, and the reverse is also true. My worker handles this by parking the job as QUOTA_WAIT. A probe resumes it later.
What happens when a usage limit hits an unattended worker#
A worker that shells out to claude -p gets a limit message back instead of the work it asked for. If the worker treats any non-success result as a failure, one of two things happens:
- The job is marked failed, and someone has to find it and requeue it.
- A retry loop starts and hammers the CLI while the limit is still active. Every retry fails the same way, and the job burns through its retry budget in minutes.
Neither is right, because a usage limit is not a job error. The job is fine and the input is fine. Nothing is wrong except that you have to wait. Treating it as a separate state keeps the failure metrics honest and keeps the retry counter for real errors.
That is the reason I added a dedicated state. When the CLI reports a usage limit, the worker sets the job to QUOTA_WAIT. It doesn't count an attempt, and it doesn't schedule a normal retry. The job stays in the queue with all its inputs intact.
How Claude Code usage limits work on a Max plan#
The parts that matter for a worker:
- Session limit (5 hours). This is the one you will hit first when the worker runs a lot of builds or reviews in a burst.
- Weekly limit. This is the slower one. If you hit it, waiting for the next session window won't help.
- Shared pool. My own chat and Claude Code use count against the same limits as the worker. I treat the worker as one more consumer of a budget I also use by hand.
This shared budget is why "how often do the limits reset" has no single answer you can hardcode. The session limit resets on a 5-hour basis, and the weekly limit resets weekly. How much of either you have left depends on what else you did. Anthropic documents plan details in the Claude Code docs and the Claude support pages. Check those for current numbers per plan, because they change and I won't quote them here.
To see where you stand by hand, open an interactive Claude Code session and use the /usage command. That is for you at the keyboard. A headless worker can't read it, which brings up the next problem.
How to see usage when claude -p shows no numbers#
Plain claude -p printed the answer text and nothing about usage. That gave me nothing to log, nothing to graph and no way to tell which jobs were expensive.
So I switched every call to --output-format json and log tokens, cache reads, turns and duration per call into SQLite. The JSON result carries those fields next to the answer text, so one parse step pulls them out.
claude -p "Review this diff for obvious bugs" --output-format json > out.json
jq '{turns: .num_turns, ms: .duration_ms, cache_read: .usage.cache_read_input_tokens, in: .usage.input_tokens, out: .usage.output_tokens}' out.json
Check the field names against your own output before you build on them, since I'm describing the shape of the JSON from the CLI and it can change between versions.
The table I log into is small. Python's built-in sqlite3 module is enough:
import json, sqlite3, time
db = sqlite3.connect("usage.db")
db.execute("""CREATE TABLE IF NOT EXISTS calls (
ts REAL, job_id TEXT, kind TEXT,
input_tokens INTEGER, output_tokens INTEGER,
cache_read INTEGER, turns INTEGER, duration_ms INTEGER)""")
def log_call(job_id, kind, raw):
r = json.loads(raw)
u = r.get("usage", {})
db.execute("INSERT INTO calls VALUES (?,?,?,?,?,?,?,?)", (
time.time(), job_id, kind,
u.get("input_tokens"), u.get("output_tokens"),
u.get("cache_read_input_tokens"),
r.get("num_turns"), r.get("duration_ms")))
db.commit()
This doesn't tell you how much of your plan limit is left. The plan percentage isn't in the per-call output I log. What it gives you is a history of what each job type costs in tokens and turns, so that when a limit hits you can see what burned it. That's the closest thing to a usage monitor I have for a headless worker.
For scale, here is what I observed on my box: a build took 35 seconds to 5 minutes, and a review took 13 seconds to 1 minute. A worker that runs builds back to back can fill a session window quickly. A review is short, so it adds up more slowly.
How to continue Claude Code after the usage limit is reached#
The design has three parts: detect, park, probe.
Detect the limit#
The worker needs to tell "usage limit" apart from every other failure. Look at the exit status and the result text of the call, and match on the limit message. Keep that matcher in one function, because the message text is the brittle part. If the wording changes in a new CLI version, a job that should have been parked will be marked as an ordinary failure. Log the raw output of any unrecognized failure so you can spot that quickly.
Park the job#
Parking means one state change and nothing else:
def park(db, job_id):
db.execute(
"UPDATE jobs SET status='QUOTA_WAIT', parked_at=? WHERE id=?",
(time.time(), job_id))
db.commit()
Do not increment the attempt counter, and do not delete partial output. If the job is a multi-step build, keep whatever state lets it start over cleanly from the queue.
Also stop the worker from taking new jobs while any job is in QUOTA_WAIT. Otherwise it pulls the next job, hits the same limit, and parks that one too. You end up with a queue full of parked jobs, each of which cost a CLI call to discover.
Probe and resume#
A separate probe runs on a timer. Its only task is to find out whether the limit has lifted. It sends one tiny request through the same CLI path the worker uses. If the request succeeds, the probe moves the QUOTA_WAIT jobs back to PENDING. If it hits the limit message again, it does nothing and waits for the next tick.
Run the probe from a systemd timer or cron. A fixed interval is enough. You don't need to compute the exact reset time. The probe finds out empirically, and a cheap call every so often costs very little next to a stuck queue.
One caution: the probe is a call that counts against the same shared pool. Keep it tiny, and don't poll every few seconds.
Can you increase, bypass or remove the limit?#
These are the searches people make, so here is the straight answer.
- Reset it: you can't. It resets on the plan's schedule.
- Increase it: the limit is tied to your plan. Moving to a higher plan tier changes it, and the plan pages list the details. Nothing in the worker changes it.
- Bypass or remove it: I don't have a method, and I'd treat any "hack" with suspicion. Anything that works around account limits risks your account and may break the terms of service. The supported route is to wait, upgrade the plan, or switch to API billing, where usage is metered on your key instead of by plan window. Read the docs for how that differs.
- Limit your own usage: since the pool is shared, the useful lever is what the worker spends. Log per-call tokens and turns, find the job types that cost the most, and shrink their prompts or the number of turns they are allowed.
What went wrong and what I'd do differently#
The first version had no way to measure anything. With plain claude -p there were no usage numbers, so when the limit hit, I couldn't say which jobs had caused it. That is why every call now uses --output-format json and writes a row to SQLite. I'd do that from day one.
The other lesson is the one the whole design rests on: a usage limit is not a failure. Before I parked jobs as QUOTA_WAIT, a limit looked identical to any other error, which means it could consume a job's retry budget for no reason. If I were starting again I'd define the states first: PENDING, RUNNING, DONE, FAILED and QUOTA_WAIT. Then I'd write the detector, and only after that the worker.
Things I'd still treat as open:
- The limit-message matcher depends on text I don't control.
- The probe consumes a little of the same budget it is waiting on.
- Because the pool is shared with my own chat use, the worker can be starved by me, and I have no way to prevent that other than watching the log.
Checklist for a worker that survives usage limits#
- Call the CLI with
--output-format jsonand parse the result instead of reading plain text. - Log tokens, cache reads, turns and duration per call to SQLite.
- Isolate the usage-limit matcher in one function, and log raw output for unrecognized failures.
- Add a
QUOTA_WAITstate that doesn't count as an attempt. - Pause new job intake while any job is parked.
- Run a small probe on a timer, and resume parked jobs only after it succeeds.
- Remember the pool is shared with your own chat and Claude Code use, so check
/usageby hand when the worker parks unexpectedly. - Check the plan documentation for current limits instead of hardcoding them.
Quick answers
How do I reset my Claude Code usage limit?
You can't reset it manually. The 5-hour session limit and the weekly limit reset on their own schedules, so the practical option is to park the work and resume it after the reset.
How do I continue Claude Code after the usage limit is reached?
Wait for the limit to lift, then run the job again. In an unattended worker, mark the job as waiting (I use QUOTA_WAIT) instead of failed, and have a small timed probe move it back to pending once a request succeeds.
How can I see Claude Code usage when running claude -p?
Plain claude -p gave me no usage numbers. Using --output-format json returns fields such as tokens and turns that you can log per call, though that is a history of your jobs and not a readout of remaining plan limit.
Are Claude Code usage limits shared with chat on Max?
On my Max plan the 5-hour session limit and the weekly limit are shared with my own chat and Claude Code use. A background worker draws from the same budget you use by hand.
Next note