AI Limit Timer
Automatically maximize your token usage on Anthropic's AI coding tools by ensuring you never lose time to rate-limit reset windows.
Why This Exists
Codex CLI and Claude Code both enforce rolling rate limits: a 5-hour window and a weekly window. When you hit a limit, the timer counts down to a reset — but the fresh allocation only begins when you actually send your next prompt. If your 5-hour window resets at 6:00 AM and you don't open your terminal until 9:00 AM, your NEXT rest window won't be until 2pm. With this tool active, your next window will be available at 11am.
This tool fixes that. It:
- Reads exact reset timestamps from Codex session logs and Claude's stream output
- Sends a minimal keep-alive prompt ("Reply with exactly OK.") right after each reset, so your next window starts immediately
- Self-schedules via macOS
launchdto wake up at the next reset time — no cron jobs, no always-on process
The result: your rate-limit windows overlap seamlessly. You get the maximum available token budget without ever having to think about reset timing.
How It Works
┌──────────────────────────────────────────────────────┐
│ ai-limit-timer │
│ │
│ 1. Check current reset times │
│ ├─ Codex: read ~/.codex/sessions/**/*.jsonl │
│ └─ Claude: parse stream-json output │
│ │
│ 2. If any window has reset → send "OK" prompt │
│ ├─ Codex: codex exec "Reply with exactly OK." │
│ └─ Claude: claude -p "Reply with exactly OK." │
│ │
│ 3. Compute next reset time (earliest across both) │
│ │
│ 4. Write launchd plist → sleep until then + 60s │
│ │
│ 5. launchd wakes us → go to step 1 │
└──────────────────────────────────────────────────────┘
After a single npm run run, the timer runs itself indefinitely via launchd. Each cycle writes a new plist with the next wake time and re-registers it — no persistent process, no cron job.
If your Mac is off or asleep when a scheduled reset passes, the job runs automatically on next login (RunAtLoad is enabled). It detects that windows have already reset, sends the keep-alive prompts, and resumes the normal scheduling chain.
Data Sources
| Provider | Source | Accuracy |
|---|---|---|
| Codex | ~/.codex/sessions/**/*.jsonl — token_count payloads with rate_limits.primary.resets_at and rate_limits.secondary.resets_at |
Exact — reads Codex's own persisted timestamps |
| Claude | claude -p --output-format stream-json — rate_limit_event objects with resetsAt and overageResetsAt |
Best-effort — if stream-json doesn't contain rate-limit events, falls back to now + 5h for the primary window and preserves the previous weekly reset |
Requirements
- macOS (uses
launchdfor scheduling) - Node.js v18+
- Codex CLI (
codex) — install guide - Claude Code (
claude) — install guide
Setup
1. Clone and configure
git clone https://github.com/shnksi/ai-limit-timer.git
cd ai-limit-timerCopy the example config and edit it:
cp ai-limit-timer.config.example.json ai-limit-timer.config.json
2. Set your workspace directories
Edit ai-limit-timer.config.json. The critical setting is workspaceDir for each provider — this is the directory the CLI runs in when sending the keep-alive prompt.
{
"codex": {
"workspaceDir": "/path/to/any/project"
},
"claude": {
"workspaceDir": "/path/to/a/trusted/project"
}
}Important: For Claude, use a directory you've already opened and trusted in interactive mode. Otherwise the trust prompt will block the automated run.
3. First run
This will:
- Check current reset times for both providers
- Send a keep-alive prompt if any window has already reset
- Save state to
./var/state.json - Install a
launchdjob to wake up at the next reset time
4. Verify
You should see output like:
Codex: session-log
5h reset: 3/17/2026, 10:05:20 AM
weekly reset: 3/18/2026, 11:39:57 PM
Claude: stream-json
5h reset: 3/17/2026, 7:00:00 AM
weekly reset: 4/1/2026, 12:00:00 AM
Next run: 3/17/2026, 7:01:00 AM
From here, the timer manages itself. It will wake up after each reset, send the keep-alive prompt, and schedule the next run.
Commands
| Command | Description |
|---|---|
npm run run |
Execute one cycle: check resets, prime if due, schedule next run |
npm run status |
Show current reset times and next scheduled run |
npm run status -- --json |
Output status as JSON |
npm run install |
Install/update the launchd daemon from saved state |
npm run uninstall |
Remove the launchd daemon |
npm test |
Run parser unit tests |
Configuration
All settings in ai-limit-timer.config.json. Only workspaceDir is required — everything else has sensible defaults.
Disabling a provider
Set "enabled": false to skip either provider entirely:
{
"codex": { "enabled": false },
"claude": { "enabled": true, "workspaceDir": "/my/project" }
}File Structure
ai-limit-timer/
├── src/
│ ├── ai-limit-timer.mjs # Main entrypoint: CLI, scheduling, orchestration
│ └── parsers.mjs # Extracts reset timestamps from Codex/Claude output
├── test/
│ └── parsers.test.mjs # Unit tests for parsers
├── var/ # Runtime data (gitignored)
│ ├── state.json # Persisted reset times and next run
│ ├── logs/ # Stdout/stderr from each keep-alive prompt
│ └── transcripts/ # Raw Claude usage captures
├── ai-limit-timer.config.example.json
├── ai-limit-timer.config.json # Your local config (gitignored)
├── package.json
└── LICENSE
Troubleshooting
Codex shows "prime-fallback" instead of "session-log"
Codex doesn't always include rate-limit timestamps in session logs for small prompts. The timer still works — it estimates now + 5h and schedules accordingly. You'll get exact timestamps again once you use Codex interactively and it writes rate_limits data to its session logs.
Claude prime fails or times out
- Ensure
workspaceDirpoints to a directory you've previously trusted in Claude Code's interactive mode. - Check
./var/logs/claude-prime-*.logfor the full stdout/stderr.
"launchd scheduling requires a local macOS user session"
The tool must run as a logged-in user, not as root or via SSH without a GUI session. launchd user agents require a GUI login context.
Next run time seems wrong
The timer picks the earliest reset across all providers and adds runDelayAfterResetSeconds (default: 60s). Check npm run status -- --json to see exact timestamps and verify which provider is driving the schedule.
Want to see what happened on the last run?
Check ./var/launchd.stdout.log and ./var/launchd.stderr.log for the launchd-triggered output, or browse ./var/logs/ for per-provider execution logs.
How the Fallback Works
When exact rate-limit data isn't available — Claude's stream-json doesn't contain a rate_limit_event, or Codex's session log has rate_limits: null for small prompts — the timer uses a conservative fallback for that provider:
- 5-hour window: estimated as
now + 5 hours - Weekly window: preserved from the previous known value if it hasn't expired, otherwise estimated as
now + 7 days
This means the timer will always schedule a next run, even with incomplete data. The worst case is waking up slightly early and sending an extra keep-alive prompt — which is harmless.