How it works (why doing this is valuable)
The Claude subscription's 5-hour usage window starts counting fromthe first request within the window, not rotating at a fixed clock time. So after the quota is exhausted, if nobody sends a message, the new window never starts counting; when the user wakes up and sends the first message themselves, it effectively starts a brand-new 5 hours from that moment. Conversely, if a scheduled task sends a minimal message on the user's behalf right after the reset moment, the new window starts counting from late at night — by the time the user wakes up, this window has already used up most of its duration (while consuming almost no quota), which amounts to hiding a cooldown period inside sleep time.
The cost of one poke is tiny: one claude -p single-turn message. The full flow was verified successful on 2026-08-29.
Entry-point triage
- User gives arelative time("resets in 1 hour 20 minutes") → wait minutes = relative minutes + 5-minute buffer.
- User gives anabsolute time("resets at 6:15") → first
dateget the current time and compute the difference; if the target clock time is earlier than or equal to the current time, always interpret it asthe next dayat the same time (saying "resets at 6:15" late at night means tomorrow morning; crossing midnight before bed is a normal input for this skill, not a user error), then add a 5-minute buffer after computing the difference. - User wantsperiodic/every-day poking → beyond this skill's boundary; use /loop or system cron, not a one-shot sleep chain.
- User asks when ChatGPT/Codex resets → tibo-reset-codex skill。
- User askswhy the quota burns so fast → claude-usage-analyst skill。
Execution steps
Step 0: Idempotency check (if a timer already exists, don't start another)
ps -ef | grep -E 'claude -p "Reply with exactly: ok"' | grep -v grep
If there is output, a ping timer is already running — report the existing timer's start time to the user, ask clearly whether to replace it, and don't silently stack a second one. To replace: follow the Troubleshooting row "user changes their mind mid-way and wants to cancel" for the identifying string, pkill the old chain, confirm with ps that it's clean, then redo the full Step 1–4 flow with the new parameters (especially Step 4 — the new timer's output path must be re-explained, otherwise if the wake-up notification is lost, the fallback path still points at the old timer that has already been killed).
Step 1: Compute the time and report it
date '+%Y-%m-%d %H:%M:%S'; date -v+<等待分钟数>M '+目标时刻 %H:%M:%S'
Conversion formula (placeholders unified throughout):等待分钟数 = 距重置分钟数 + 5;SLEEP_SECONDS = 等待分钟数 × 60.
Example: "resets in 1 hour 20 minutes" → 85 minutes → SLEEP_SECONDS=5100.
Report the specific trigger moment (what hour and minute) to the user; don't just say "in 85 minutes" — what the user wants to know before bed is the wall-clock time.
Step 2: Start the timer process (run_in_background)
With the Bash tool,run_in_background: true run:
caffeinate -is /bin/sh -c 'sleep <SLEEP_SECONDS> && cd "$HOME" && claude -p "Reply with exactly: ok"'
must be run_in_background: A foreground sleep will be intercepted by the harness; a background task is a detached process, and when it exits it will re-wake the current session (see Step 5). The startup receipt contains the task output file path—remember it, Step 4 needs to write it into the explanation to the user (this is the only reliable opportunity to persist the path).
Step 3: Independent read-back verification
The startup receipt does not count; use ps to confirm the process chain really exists:
ps -ef | grep -E 'caffeinate -is|sleep <SLEEP_SECONDS>' | grep -v grep
Expected output: at least three lines—/bin/sh -c sleep ...、caffeinate -is ...、sleep <SLEEP_SECONDS>.
If the sleep line is missing, it means the process did not start; go back to Step 2 and check the error.
Step 4: Explain clearly to the user
- Timestamp: the specific wall-clock time.
- Output file path: copy the task output file path from the startup receipt in Step 2 verbatim into the explanation. Once the trigger time and path are in the conversation history, even if the context is compressed after 85 minutes or the wake-up notification is lost, you can still use them to recover the scene (the Troubleshooting fallback relies entirely on this).
- Precondition: do not shut down the machine, do not close the lid (
caffeinate -isprevents automatic sleep, but it cannot stop physically closing the lid). - Redundant design: even if the terminal/session is closed, the poke will still execute as normal (
claude -pis an independent process); if the session remains open, when the time comes you will also be woken up to help the user confirm the result.
Step 5: Wrap up after being woken
When the scheduled process exits, the harness will wake this session with a task-notification. At this point:
- Read the task's output file (the notification includes the path; if lost, use the one recorded in the Step 4 explanation).
- Expected content:
ok+[exited with code 0]—indicating thatclaude -pactually sent a request and got a reply, and the new window has started counting from the actual trigger time. - The actual trigger time is based on the output file's modification time:
stat -f '%Sm' <output文件>. If it deviates from the planned time in Step 1 by more than a few minutes, it means the machine slept in between and the sleep was postponed— when reporting to the user, use the actual time; the new window's next reset time = actual trigger time + 5 hours. - Also note that caffeinate has been automatically released when the process exited; there are no leftover processes to clean up.
Being woken up is itself an API request, and together with claude -p they provide mutual redundancy—either one occurring can open the window.
Command anatomy (read this before changing any component)
| Component | Why it is here, what happens if you change it |
|---|---|
caffeinate -is |
Prevents idle sleep (-i) and system sleep when plugged in (-s), holding through the waiting period; sleep does not count time while the machine is asleep, so the poke will be delayed until wake. The process releases it automatically when it ends. It cannot prevent physically closing the lid. |
sleep <秒> |
The simplest carrier for a one-shot timer. On macOS, do not rely on at; the ScheduleWakeup tool's delay is capped at 3600 seconds and cannot sustain longer waits. |
cd "$HOME" |
Avoid starting claude -pin the project directory—it will load the project CLAUDE.md and hooks, wasting thousands of tokens and possibly triggering project side effects. |
claude -p "Reply with exactly: ok" |
Single-turn print mode; it replies with one word and exits, without exploring or doing work. This is the "poke" itself: one real API request. Do not specify --model(reduces the parameter failure surface; a single default-model call in a just-reset window is negligible). |
run_in_background |
The source of the redundancy: the detached process does not depend on the session staying alive; when it exits, it also wakes the session to confirm the result. |
| +5 minute buffer | Reset times estimated from the user's verbal account have errors; a timestamp earlier than the reset time hits the old window and is pure waste |
Troubleshooting
| Symptom | Handling |
|---|---|
| Startup command blocked by sandbox (caffeinate reports a permission error) | Use dangerouslyDisableSandbox: true to retry once |
| Foreground sleep rejected | This means you forgot run_in_background: true, not that sleep is unavailable |
The output file does not contain ok but rather a rate-limit/network error |
You timestamped too early (the old window has not ended yet) or there is a network problem. Read the specific error; if it is a rate limit, the buffer was insufficient—start another short 10-15 minute timer to make up the timestamp |
| Not woken at the scheduled time | Use the path recorded in the Step 4 instructions to read the output file directly (the timestamp itself may already have succeeded). If the path is also lost, use ps to check the process chain:sleep still present = the time has not arrived; chain gone and the scheduled time has passed = it has already executed. When you cannot read any local evidence, honestly say "cannot confirm locally," ask the user to check the usage status in the product, and do not falsely claim it has been confirmed |
| The user changes their mind midway and wants to cancel | pkill -f 'Reply with exactly: ok'—match by command feature string; as actually tested on 2026-08-29 this can kill caffeinate and sh; the leftover orphan sleep is harmless (sh is already dead,&& will not execute afterward), and it dies on its own at the scheduled time.Do not pkill -f 'sleep <SLEEP_SECONDS>'by seconds: the heartbeat sleep of a long-running script on this machine may happen to use the same number of seconds, and accidental kills have actually occurred in testing |
Boundaries
- macOS only:
caffeinateanddate -vare both macOS-specific. Their Linux counterparts (systemd-inhibit、date -d) have not been verified by this skill—when encountering a non-macOS environment, plainly state that it is unsupported and do not improvise a port on the spot. - One-offtask-specific. Periodic keep-alive and daily scheduling belong to /loop, system cron, or launchd.
- Only timestampsClaude CLI accounts already logged in on this machine; it does not involve API key billing accounts (API pay-as-you-go has no window concept).
- Does not query or predict reset times—reset times are based on the user's verbal account or what is displayed in the product; reset queries on the Codex side belong to tibo-reset-codex.
- It cannot fulfill the commitment while the machine is shut down or the lid is closed; this is an inherent boundary of this local approach. If you cannot accept it, do not promise it—clearly tell the user.