Agent quota warmup (agent-warmup)¶
agent-warmup starts the Claude Pro/Max 5-hour subscription usage window at
a scheduled time (e.g. 06:00 on workdays), so a fresh window is already running
before you begin real work — plus an ad-hoc "warm up at this time" one-shot.
The CLI lives at dot_dotfiles/bin/executable_agent-warmup (deployed to
~/.dotfiles/bin/agent-warmup, on PATH). Claude-only by design — Codex /
OpenCode have different quota systems.
Why it drives an interactive session (not claude -p)¶
The 2026-06-15 billing split changed everything
Since 2026-06-15, only interactive Claude Code usage advances the
rolling 5-hour subscription window. Headless claude -p (and the Agent
SDK, GitHub Actions, third-party agents) now draws from a separate metered
Agent SDK credit pool — it does not move the window. A claude -p
"warmup" would silently burn metered API credit and accomplish nothing.
See pitfalls/headless-claude-p-does-not-move-5h-window.md.
So agent-warmup run drives a real interactive claude TUI:
- injects a transient
warmup-<ts>session into your existing (GUI-login) tmux server — not a daemon-owned socket (see the box below); - in an isolated cwd (
$XDG_CACHE_HOME/agent-warmup/claude) so no projectCLAUDE.md/ context is loaded; - with
ANTHROPIC_API_KEYunset (env -u) so it uses your subscription auth (an API key would route to metered credit and not move the window); - accepts the first-run folder-trust prompt, sends a tiny prompt + Enter, waits
for the reply, logs the pane (and
/usagewith--verify), then kills that session (unless--keep).
Default model is $AICAP_CLAUDE_MODEL (→ haiku) to keep window-token
consumption tiny.
Why it borrows your tmux server (and the constraint that follows)
claude's TUI renders nothing when its tmux server was created by a
background daemon (launchd/pueue) — but renders fine when a daemon connects
to your already-running GUI-session server and spawns claude there. So the
scheduled run injects into your default tmux server. Constraint: a tmux
server must be running in your login session at warmup time. If none exists
and the caller is not a TTY, run notifies and exits rather than silently
no-op'ing. Full four-trap story (uv launcher hang, amfid/pyexpat stall,
secondary-volume Python, daemon-server blank pane):
pitfalls/agent-warmup-scheduled-run-hangs-or-blank-under-daemon.md.
Confirmed working (2026-06-20)
Both backends verified end-to-end (pueue one-shot and launchd timer):
the scheduled run rendered claude, sent the prompt, got ⏺ hi, and captured
/usage showing the usage on the "Current session" (5-hour) bucket with
the metered "Usage credits" pool off — i.e. genuine interactive
subscription usage that advances the window.
Commands¶
# One-off: warm up now, keep the session + capture /usage for inspection
agent-warmup verify
# Recurring: 06:00 every weekday (launchd on macOS, systemd user timer on Linux)
agent-warmup install --daily weekdays --time 06:00
agent-warmup uninstall
# Ad-hoc one-shot via pueue
agent-warmup at --at '06:00'
agent-warmup at --delay 8h
# Inspection
agent-warmup status # timer state + pending one-shots + recent run logs
agent-warmup status --json
agent-warmup cancel # remove pending one-shots
# The primitive the schedulers call
agent-warmup run [--model haiku] [--prompt 'Reply with just: hi'] [--timeout 120] [--keep] [--verify]
at and install forward the shared run flags (--model, --prompt,
--timeout, --verify) into the scheduled run.
Backends¶
| Schedule | macOS | Linux |
|---|---|---|
Recurring (install) |
~/Library/LaunchAgents/com.<user>.agent-warmup.plist (StartCalendarInterval), loaded with launchctl bootstrap; validated with plutil -lint |
~/.config/systemd/user/agent-warmup.{service,timer} (OnCalendar=, Persistent=true), systemctl --user enable --now; validated with systemd-analyze verify |
One-shot (at) |
pueue group agent-warmup, label agent-warmup:oneshot:<ts> |
same |
The recurring timer is imperative and opt-in — generated by install, it is
not chezmoi-managed (so there is no .chezmoi.toml.tmpl prompt and no forced
install on every machine). Run install on the host where you want the warmup.
Both install and at invoke run through a main-volume Python
(/opt/homebrew/bin/python3), not the uv run shebang — the uv launcher and a
uv interpreter on a secondary volume both hang under a daemon bootstrap (see the
pitfall). The script is stdlib-only, so any python3.11+ works.
Caveats¶
- A tmux server must be running in your login session at warmup time — the
run borrows it (a daemon-created server renders a blank claude). If none is
running and the call isn't a TTY,
runnotifies and exits. - Machine asleep at the scheduled time → the job fires on wake (launchd
coalesces missed calendar events; systemd uses
Persistent=true), so the window geometry shifts to wake time. - Keychain must be unlocked (you must be logged into the GUI session) for subscription auth.
- Auth expiry / blank pane →
runlogs the capture and fires a desktop notification (statuswarning: blank pane) rather than reporting a falseok. - Brief workspace blip → a
warmup-<ts>session appears in your tmux session list for ~1 minute, then is killed.
Relationship to agent-wakeup¶
agent-warmup is a separate tool from
agent-wakeup (quota recovery — scheduling a
continue into an existing blocked pane). Warmup needs no existing pane, starts
a fresh isolated session, and must not continue real work. The broader
auto-continue loop and a unified dashboard remain backlog items.