How I Automated Claude's 5-Hour Window Reset on macOS (Without an API Key)
Never Miss Your 5-Hour Claude Code Window Again: Complete Setup Guide

How I Automated Claude Code's 5-Hour Window Reset on macOS (Without an API Key)
If you're a Claude Pro subscriber and use Claude Code heavily, you've probably hit the 5-hour usage window at the worst possible time — right in the middle of a coding session. I spent a weekend figuring out how to automate the reset, and the journey was messier than I expected. Here's exactly what I tried, what failed, and what finally worked.
The Problem: The 5-Hour Rolling Window
Claude Code has a rolling 5-hour usage window. Once you send your first prompt, the clock starts. Five hours later, the window resets and you get a fresh quota. The catch is that the window starts from your first prompt — not at a fixed time like midnight. So if you start working at 10 AM, your window expires at 3 PM, right when you're deep in the zone.
The fix is simple in theory: send a tiny warmup ping before you start your actual work so the window resets at a convenient time instead of mid-session.
The command is:
claude -p "hi" --model claude-haiku-4-5
That's it. A single "hi" to Haiku costs almost nothing but starts the 5-hour clock from that moment.
My Schedule
I wanted the window to reset at 2:00 PM every day — the start of my main coding block. Working backwards with a 5h 6m interval (I added 6 minutes as buffer):
| Slot | Time (IST) |
|---|---|
| 1 | 12:12 AM |
| 2 | 8:54 AM |
| 3 | 2:00 PM ← anchor |
| 4 | 7:06 PM |
The critical chain is 12:12 AM → 8:54 AM → 2:00 PM → 7:06 PM. Each slot starts a fresh 5-hour window, ensuring a clean window opens at 2 PM every day.
Attempt 1: GitHub Actions (Failed)
My first instinct was GitHub Actions — free, cloud-based, runs even when my machine is off. I created a repo, wrote the workflow YAML, and added my token as a secret.
The workflow looked clean:
name: Claude Warmup
on:
schedule:
- cron: '30 8 * * *' # 2:00 PM IST
# ... other slots
jobs:
warmup:
runs-on: ubuntu-latest
steps:
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Fire warmup ping
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_TOKEN }}
run: claude -p "hi" --model claude-haiku-4-5
I ran it. Two errors back to back.
Error 1:
API Error: 404 {"type":"error","error":{"type":"not_found_error","message":"model: claude-3-5-haiku-20241022"}}
The --model haiku alias was resolving to an old deprecated model ID. Fixed it by using the full model ID: claude-haiku-4-5.
Error 2:
Not logged in · Please run /login
This is where GitHub Actions hit a wall. The CLAUDE_CODE_OAUTH_TOKEN environment variable works fine on your local machine where Claude Code is already set up. But on a fresh GitHub Actions runner, the CLI has no login state and the OAuth token isn't recognized as authentication in that context.
The official solution is to use ANTHROPIC_API_KEY — a proper API key from console.anthropic.com. But here's the thing: Claude Pro subscribers don't have API keys. The API is a separate product with separate billing. As a Pro subscriber, all I have is the OAuth token from claude setup-token.
GitHub Actions was a dead end for me.
Attempt 2: macOS LaunchAgent (Works Perfectly)
Since GitHub Actions needed an API key I didn't have, I went back to running this locally on my Mac. The obvious concern: what happens when my Mac is sleeping?
After checking Apple's launchd documentation and developer forums, I found the answer:
"If you schedule a launchd job using StartCalendarInterval and the computer is asleep when the job should have run, your job will run when the computer wakes up."
This is the key difference between cron and launchd. Cron skips missed jobs entirely. launchd queues them and fires the moment the Mac wakes. Multiple missed slots get coalesced into a single run — which is fine, since one ping is all I need.
Do You Need to Log In as Root?
No. LaunchAgents in ~/Library/LaunchAgents/ run in your user context, not as root. They have full access to your ~/.claude/ directory where Claude Code stores your login credentials. As long as you're logged into Claude Code in your terminal, the LaunchAgent picks up those credentials automatically.
LaunchDaemons (in /Library/LaunchDaemons/) run as root and would be a different story — but we're using a LaunchAgent, so this isn't an issue.
The Setup
Prerequisites
The warmup script uses openssl to append a random suffix to each ping (so Claude never serves a cached response). Install it via Homebrew if you don't have it:
brew install openssl
Verify it works:
openssl rand -hex 4
You should see an 8-character hex string like a3f2b1c0.
Step 1: Create the warmup script
mkdir -p ~/.claude-warmup
Create ~/.claude-warmup/warmup.sh:
#!/bin/bash
LOG="$HOME/.claude-warmup/warmup.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
MESSAGES=(
"Hi! How are you doing today?"
"Hey, what's the weather like today?"
"Hi there! Any fun facts to share?"
"Hello! What's a good productivity tip for today?"
"Hey! Tell me something interesting."
"Hi, how's your day going?"
"Hello there! What's a quick tip for staying focused?"
"Hey, what's a random piece of trivia?"
"Hi! What's something cool happening in tech today?"
"Hello! Any good book recommendations?"
)
RANDOM_MSG="\({MESSAGES[\)((RANDOM % ${#MESSAGES[@]}))]}"
{
echo "[$TIMESTAMP] Firing warmup ping..."
/opt/homebrew/bin/claude -p "$RANDOM_MSG" --model claude-haiku-4-5 2>&1 | while IFS= read -r line; do
echo "[\(TIMESTAMP] \)line"
done
echo "[$TIMESTAMP] Done."
} >> "$LOG"
# Keep only last 2 days
TODAY=$(date '+%Y-%m-%d')
YESTERDAY=$(date -v-1d '+%Y-%m-%d')
awk -v today="\(TODAY" -v yesterday="\)YESTERDAY" '
/^\[/ { d = substr($0, 2, 10) }
d == today || d == yesterday
' "\(LOG" > "\)LOG.tmp" && mv "\(LOG.tmp" "\)LOG"
Make it executable:
chmod +x ~/.claude-warmup/warmup.sh
Note: If you're not on Apple Silicon, find your claude path with
which claudeand replace/opt/homebrew/bin/claudeaccordingly.
The script handles its own logging — every line including Claude's response gets a timestamp prefix. After each run it trims the log to the last 2 days automatically, so the file stays small forever.
Step 2: Create the LaunchAgent plist
Create ~/Library/LaunchAgents/com.claude.warmup.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.claude.warmup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/YOUR_USERNAME/.claude-warmup/warmup.sh</string>
</array>
<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>8</integer><key>Minute</key><integer>54</integer></dict>
<dict><key>Hour</key><integer>14</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>19</integer><key>Minute</key><integer>6</integer></dict>
<dict><key>Hour</key><integer>0</integer><key>Minute</key><integer>12</integer></dict>
</array>
<key>RunAtLoad</key>
<false/>
</dict>
</plist>
Replace YOUR_USERNAME with your actual macOS username (run whoami if unsure).
Adjust the times to your timezone. The times above are IST (UTC+5:30). To calculate your own schedule, pick your anchor time, go backwards two slots (subtract 5h 6m each time), then forwards one slot (add 5h 6m).
Step 3: Load the LaunchAgent
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude.warmup.plist
Step 4: Test it
# Fire it manually
launchctl start com.claude.warmup
# Check the log
cat ~/.claude-warmup/warmup.log
You should see something like:
[2026-06-08 14:00:01] Firing warmup ping...
[2026-06-08 14:00:03] Take regular breaks — they actually improve focus by preventing mental fatigue.
[2026-06-08 14:00:03] Done.
Every line is timestamped. The log auto-trims to the last 2 days after each run.
How to Adjust Your Anchor Time
If your work schedule changes, recalculate your 4 slots by picking a new anchor, going backwards twice and forwards once:
Anchor: 3:00 PM
Backwards: 3:00 PM → 9:54 AM → 12:48 AM (previous day)
Forwards: 3:00 PM → 8:06 PM
Final slots: 12:48 AM, 9:54 AM, 3:00 PM, 8:06 PM
Update the plist, then reload:
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.claude.warmup.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude.warmup.plist
Does This Affect Battery Life?
I looked into this before setting it up, because I didn't want a background daemon silently draining my MacBook.
The short answer: no noticeable impact at all.
Here's why. The LaunchAgent is not a background process. It doesn't sit in memory between runs. It doesn't poll anything. It doesn't prevent sleep. The way launchd works, the job is completely dormant until the scheduled time hits — at which point macOS wakes it, runs the script, and kills it. The whole thing takes 3–5 seconds, four times a day.
That's roughly 15 seconds of total CPU activity per day, plus 4 tiny HTTP requests to Anthropic's servers. For comparison, a single webpage load costs more than the entire day's warmup traffic.
The log trimming with awk adds nothing measurable — it runs on a file that's never bigger than ~30 lines and finishes in under a millisecond.
I also confirmed the LaunchAgent does not wake the Mac from sleep on its own. It catches up on missed jobs when the Mac wakes for other reasons (like you opening the lid). So it's not adding any extra wake cycles to your sleep schedule either.
Bottom line: this setup has zero real-world impact on battery life.
What This Does NOT Do
This resets the 5-hour rolling window timing, not the weekly usage cap. If you hit the weekly limit, no amount of warmup pings will help — you just have to wait for the week to reset.
Summary
GitHub Actions fails for Pro subscribers because it needs an API key, not an OAuth token
macOS LaunchAgent is the right tool for local scheduling — it catches up missed jobs on wake from sleep
No root login needed — LaunchAgents run in your user context and read your existing Claude Code credentials
The whole setup is 3 files, 2 commands, and works forever with zero maintenance
The log file at ~/.claude-warmup/warmup.log gives you a full history of every ping so you can verify it's working anytime.
