If you're on a Claude subscription, recent versions of Claude Code (v2.1.234+) do something quietly great: when a usage limit stops Claude mid-task, the session waits and continues the task on its own after the limit resets.
Usage limit reached · limit resets 3:45pm
Continuing automatically at 3:45pm · esc to cancel
That's exactly what you want for a long refactor you start before bed. There's one catch, straight from the docs:
If it slept for more than about 30 minutes and the limit reset while it slept, the first line reads
Your usage limit has resetand the second readsPress enter to continue.
So if your MacBook goes to sleep during the wait — lid closed, or just idle sleep — you wake up to a prompt asking you to press Enter, and the work hasn't moved. The 5-hour window means the wait can easily be a few hours.
(One more catch, pointed out by John Reed in the comments: the wait is interactive-only. Background sessions and claude -p runs don't get it — a rate-limit stop there is just a stop.)
Keeping the Mac awake through the wait
The wait itself uses almost no CPU, which is the problem: from the outside, a session waiting for a reset looks idle.
DIY: if you're at a desk on power, caffeinate -i in another terminal (or caffeinate -w <pid> for the Claude process) blocks idle sleep for as long as Claude runs. Closing the lid on battery is different — macOS sleeps on lid close unless you're in clamshell mode with an external display, and the only switch for that is sudo pmset -a disablesleep 1 (remember to set it back to 0).
Know when the wait starts and ends. Claude Code's hooks tell you exactly:
-
StopFailurewith"error": "rate_limit"fires when a turn ends because of a rate limit. -
Notificationwithnotification_type:-
quota_auto_resume_fired— Claude continued after the reset -
quota_auto_resume_stale— the reset happened while the computer slept for 30+ minutes; it's waiting for Enter -
quota_auto_resume_disabled— the wait ended without continuing
-
A minimal logger to see it happen:
{
"hooks": {
"StopFailure": [{ "matcher": "rate_limit", "hooks": [{ "type": "command",
"command": "echo \"$(date) limit hit\" >> ~/claude-limit.log" }] }],
"Notification": [{ "matcher": "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled",
"hooks": [{ "type": "command", "command": "cat >> ~/claude-limit.log" }] }]
}
}
With those two signals you can keep the Mac awake from the rate_limit stop until quota_auto_resume_fired (or a few minutes past the 5-hour window), and let it sleep otherwise.
What I did in Lidnight
I build Lidnight, a menu bar app that keeps a MacBook awake with the lid closed while AI agents work and lets it sleep when they're done. It installs the StopFailure hook too: when Claude Code stops on a rate limit, the session shows "limit · resumes in 2h 14m" and the Mac stays awake through a 5-hour wait, so the automatic continue actually happens. quota_auto_resume_fired puts it back to "working".
Update (Oct 17): for the cases Claude Code won't continue on its own — a weekly limit, a stale/disabled wait, or Codex — Lidnight now sends a one-line "please continue" after the reset through official features (Claude Code's cross-session messaging, codex queue). You choose per AI whether that should happen; set it to "paused" and the session stays stopped.
The same docs pass also turned up two notification types I now count as "waiting for you": elicitation_url_dialog (an MCP server asks you to open a URL) and agent_needs_input (a background session needs input). And a reader on Qiita pointed out a subtle one: a sandboxed command's network-access prompt doesn't run PermissionRequest hooks — only the permission_prompt notification catches it (after about six seconds). If you approve prompts from a hook, keep a Notification hook around too.
Lidnight is free with the lid open (AI Auto mode, you turn it on); Pro keeps going with the lid closed, even on battery, and adds auto-start, phone approvals and more — a one-time $9.99 (50% off through Oct 31) with a 7-day trial. brew install whereisdw/tap/lidnight
Sources: Claude Code hooks reference · Wait for a usage limit to reset
Top comments (4)
This one found a bug in my docs, so thank you, that's the useful kind of post…
I build cairn, a work harness for Claude Code, and its headless mode runs whole phases unattended inside a token budget, the kind of thing you kick off before bed. The verb file literally says "a usage limit is NOT a kill: the run pauses and resumes itself when the window rolls over, and batch mode carries no defences against limit-time agent loss, because the platform no longer loses them." Which was true the day I wrote it and is exactly wrong on a laptop that sleeps, because quota_auto_resume_stale is agent loss at limit time, the run is sitting there waiting for Enter and the tracker issue it was narrating to has gone silent for six hours with no explanation. The three events you dug out of the hooks reference are the fix: a StopFailure on rate_limit posts "paused, resets at 3:45pm" on the run's tracker issue and writes it into the continuity handoff, quota_auto_resume_fired posts "resumed," and stale posts "waiting for a human to press Enter," so the person reading the board at 7am sees why nothing moved instead of guessing. Same for agent_needs_input, that whole "waiting for you" class belongs on the tracker, not in a terminal nobody's looking at.
Second thing it broke: cairn measures how long an issue took from the claim comment to the close, because the agent's own estimates were running six to fourteen times over reality, and a five-hour reset wait inside that span is dead time the measurement currently eats. Those same two events bound the pause, so it can be subtracted or at least recorded beside the number…
Keeping the Mac awake I'm leaving to you and caffeinate, that's the OS's job and a plugin pretending otherwise is how you get a plugin that does sudo pmset. Two questions, since you've read the reference closer than I have: does quota_auto_resume_stale fire at reset time or at wake time, because a hook that wants to post "waiting for Enter" cares which, and do any of these fire in a claude -p headless session, or only in the interactive TUI?
Thanks — glad it caught something real. Posting the pause/resume state to the tracker is a great idea; "why did nothing move overnight" is exactly the question it answers. Two details from the docs that may matter for cairn: after repeated limit hits the wait re-arms at most twice in a row, then stops with "Automatic continue stopped…" and
quota_auto_resume_disabledfires — worth treating as a hard stop on the board. Andstaleonly happens after roughly 30+ minutes of sleep; shorter sleeps resume on their own. For your timing data,StopFailure(rate_limit) →quota_auto_resume_firedgives you a clean interval to subtract.The re-arm limit is the one I'd have missed, thank you…
Two hits and then "Automatic continue stopped" means disabled is a different animal from the other two, the session is alive and nothing will ever move it again without a human, so on the board it's "stopped, restart by hand" and not "paused," and the morning handoff has to say stopped too or the next session reads it as a pause and waits with it. That's now in the issue, and so is the interval you named, limit stop to fired, as the window the duration math subtracts.
Going back to the docs for your two points turned up a third that changes my own claim more than yours did. The wait is interactive only, the page says it outright, background sessions and -p runs don't get it at all. Cairn's walk-away batch mode runs inside the TUI session, so it's covered, but anything I launch headless under claude -p gets no wait and a rate-limit stop there is just a stop, which answers my second question to you and not in the direction I wanted. Also nothing in either payload carries the reset time, StopFailure is error plus details plus the last message, Notification is message plus type, so "paused, resets at 3:45pm" has nowhere to read 3:45 from except the message text, and the comment has to say "paused on usage limit" and the time it noticed, with the reset time only when it can parse one. And on stale firing at reset versus at wake, the docs don't say, so the recorder stamps its own clock and never calls that the reset…
That last bit is the thing I keep relearning this week: the docs' sentence and the hook's payload are two different promises, and the one you can build on is the payload.
"Stopped, restart by hand" vs "paused" is the right split — that's how I treat
disabledtoo.On the missing reset time: I hit the same wall and stopped looking for it in the hook payload. Lidnight takes it from the official usage readout instead —
/usageand the status line'srate_limitsboth carry the window reset timestamps — and the hook only marks that the session stopped. If cairn already reads the status line, that may be a cleaner source than parsing the message text.And thanks for checking
-p: no wait in headless runs is worth knowing. I'll add a line about it to the post.