跳轉到

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:

  1. injects a transient warmup-<ts> session into your existing (GUI-login) tmux servernot a daemon-owned socket (see the box below);
  2. in an isolated cwd ($XDG_CACHE_HOME/agent-warmup/claude) so no project CLAUDE.md / context is loaded;
  3. with ANTHROPIC_API_KEY unset (env -u) so it uses your subscription auth (an API key would route to metered credit and not move the window);
  4. accepts the first-run folder-trust prompt, sends a tiny prompt + Enter, waits for the reply, logs the pane (and /usage with --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, run notifies 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 panerun logs the capture and fires a desktop notification (status warning: blank pane) rather than reporting a false ok.
  • 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.