Install
$ agentstack add skill-willhsiaonyc-claude-code-skill-otsukare-claude-code-skill-otsukare ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
otsukare — usage-limit guardrail
お疲れ — "good work, go rest," then pick the work back up. Invoke this at the start of a long or token-heavy task. otsukare does not do the work itself; it wraps it in a guardrail that watches the 5h/7d rate limits, pauses safely before a limit is exhausted, and auto-resumes just after the limit resets — even if the session is hard-cut by a sudden usage spike.
> Prerequisite: otsukare can only see your usage if your statusline mirrors it to the > data file — last-statusline-input.json in your Claude config directory. See the project > README ("Expose your limits to your local agent"). If that file is missing, tell the user > and stop — otsukare is blind without it.
Constants (edit to taste)
- Helper: `
— the absoluteotsukare_usage.py` path you pin at preflight (see Resolve your tools once below) - Data file (read-only): the
mirrorpath returned by--resolve-paths(the statusline rewrites it on every render) - State dir: the
state_dirreturned by--resolve-paths— holds the checkpoint, the.heartbeat, and the.state.jsonrun-metrics file SOFT=90,HARD=97,STALE_BUFFER=8,STALE_AGE=120s,RESUME_OFFSET_MIN=10, retry tickevery 5 min,HEARTBEAT_STALE=15min
The helper already applies SOFT/HARD/STALE_BUFFER/STALE_AGE/RESUME_OFFSET_MIN; override via --soft, --stale-buffer, etc. or OTSUKARE_* env vars if needed.
Hard constraint on the wrapped work
Do not dispatch run_in_background subagents during an otsukare run. Background agents return control immediately and finish later, so you cannot block on them at a seam — their output could land after the checkpoint and be lost. Use foreground/blocking subagents only, so "let in-flight subagents finish" is actually enforceable. If a background agent is unavoidable, record its task ID in the checkpoint and reconcile it in resume mode.
At start (preflight, refresh, then arm the safety net)
Resolve your tools once, reuse them below. Different OSes expose Python under different names and PowerShell does not expand ~, so pin these tokens before running anything else and reuse them verbatim:
- `
— your Python launcher. Trypy(the Windows launcher), thenpython3, thenpython; keep the FIRST whose--version` exits 0. Never run a bare interpreter with no script — it opens a REPL or, on Windows, hits the dead Microsoft Store stub. - `
— the absolute path tootsukareusage.py, i.e./otsukare/scripts/otsukareusage.pywith any~` already expanded. - `
//— once Step 0 confirms otsukare applies, run--resolve-paths --session-id --task-slug**once** and keep the returned absolute paths (the call also emitshome,state_dir, andmirror). Takefrom the statusline mirror blob andfrom a short kebab of the task. Use these tokens everywhere below — never write a~/...` path by hand.
Step 0 — confirm otsukare even applies here. It only guards subscription 5h/7d limits:
--preflight
applicable: true→ continue.applicable: false→ show the user themessageverbatim and STOP — do not arm, checkpoint, or wrap anything. Reasons:no_rate_limits= an API / usage-billed plan (metered per token, no rolling windows) — otsukare has nothing to guard;no_mirror= the statusline mirror isn't wired (see README);stale= the mirror isn't updating.
Once applicable, set up the dead-man's switch so a sudden usage spike that hard-cuts the session still auto-resumes:
- Block until usage is fresh — do this FIRST. It is the guard against reading a stale reset/usage left by a previous session or window:
`` --wait-fresh ` This blocks (up to 30s) until the statusline has re-rendered the mirror file with a **current 5h window** (reset in the future), then returns {"fresh": true, "resets_at": , ...}`. The statusline re-renders every few seconds while the session is active, so this normally returns in under a second.
fresh: true→ proceed; the subsequent--armand decision reads now see current data.timed_out: true/fresh: false→ the mirror isn't updating (statusline not wired, or the session is idle). Tell the user usage couldn't be refreshed, then proceed on the conservative path (--armwill returnprovisional: trueand the first seam will re-point it).
Never read the plain decision/--arm for the initial reset time without clearing this barrier first.
- Write the initial checkpoint to `` (the path you resolved above). Use the template in Pause protocol step 3, filling Goal / Session ID / Task slug / cwd / branch and leaving Done/Next to be updated at each seam.
- Bump the heartbeat:
--touch-heartbeat - Initialize run metrics (baselines for the end-of-task summary):
`` --state --state-action init --task "" ``
- Arm the safety-net cron. Get the target + cron string:
`` --arm ` This prints {"safety_target": epoch, "cron": "/5 H D M ", "provisional": bool, ...}. Call the **CronCreate** tool with cron = that string, recurring: true, durable: true, and prompt = the resume prompt (see *Schedule the resume*). Record the returned job ID in the checkpoint. If provisional: true`, note that the first seam will re-point it.
Then begin the work.
Run loop
Do the wrapped work in steps. After each natural seam — a foreground subagent batch returns, a plan/todo step completes, or you make a commit (never mid-edit) — do a seam check:
- Emit a one-line seam note to the user, e.g.
✓ step 3/8 done — checking usage. This is required, not cosmetic: emitting text triggers a statusline render, which refreshes the data file so the next read is fresh. - Update progress + heartbeat: rewrite the checkpoint's
## Done/## Next, then bump the heartbeat —--touch-heartbeat. - Run the helper:
```
`` It prints JSON: {"decision": "...", "now": epoch, "stale": bool, "stalewindow": bool, "limits": {...}, "bindingresetsat": epoch|null, "resumetarget": epoch|null}`.
- If
stale_windowistrue, the reading is from an already-reset window — refresh before trusting it. Aresets_atat or beforenowmeans the mirror still holds a previous window's blob (its usage % is pre-reset and no longer applies), so the helper has flagged that limitrolledand ignored its percentage. This is exactly the "it paused at 97% right after the limit reset" failure. Block for a fresh render and re-read, then act on the refreshed decision:
``` --wait-fresh
`` If --wait-fresh still times out (fresh: false`), the statusline isn't updating — do not pause on the stale numbers; keep working and re-check at the next seam.
- Re-point the safety net if the 5h window rolled. If
limits.five_hour.resets_atis later than the currently-armed target minus the offset (or the armed target was provisional),CronDeletethe old job and re-arm via--armso the net always points at the next reset. Update the job ID in the checkpoint. - Act on
decision:
continue→ keep working.soft→ if the remaining work is near a finish line (last step, or one more small foreground batch), push through to finish — but re-check at every seam and never crosshard. If substantial work remains, run the Pause protocol.hard→ run the Pause protocol at this seam, no exceptions.
If stale is true (file is old but the window is still current), the helper has already added STALE_BUFFER to the effective usage (it rounds against you). Trust the decision. stale_window is the different case above — an expired window whose percentage is discarded, not buffered.
Pause protocol
When otsukare decides to pause proactively:
- Drain, never hard-kill. Stop dispatching new subagents; let the current foreground subagent finish (control returns naturally). Do not
TaskStopmid-work. - Reach a clean seam.
- Finalize the checkpoint ``:
```markdown # otsukare checkpoint —
- Goal:
- Session ID:
- Task slug:
- Absolute cwd:
- Branch:
- WIP SHA:
- Binding limit: , resets_at ()
- Resume target: ()
- Safety-net cron job ID:
- Outstanding background agents:
## Done
## Next
```
- Commit WIP and record the SHA into the checkpoint:
`` git add -A git commit -m "wip(otsukare): checkpoint before usage pause" git rev-parse HEAD # paste into the checkpoint's "WIP SHA" line ``
- Re-point the safety net to the binding reset if it differs from the armed target (e.g. the 7d limit binds, days later):
CronDeletethe old job, thenCronCreatewith the cron from--cron-for. Update the job ID in the checkpoint. - Record the pause in run metrics:
`` --state --state-action pause --pct ``
- Notify (single message): current used %, where you stopped, the checkpoint path, the binding limit, and the resume target as local time.
Schedule the resume
The resume prompt used by both the safety-net cron and the proactive re-point:
> otsukare resume mode. Checkpoint: . Binding reset epoch: . Follow the Resume mode section of the otsukare skill.
Embed the absolute checkpoint path (the resolved ``) directly in the prompt — the future session reads it before it has resolved anything of its own.
For the proactive re-point, get the cron string with:
--cron-for
It prints */5 H D M * — every 5 min, but only during the target hour/date, so it retries ~12× around the reset and does not fire on other days.
Resume mode
When a scheduled prompt fires (either the safety net or a proactive re-point):
First, re-pin your tools and paths. A cron prompt starts a fresh session, so re-resolve ` and exactly as at preflight, then recover the original run's paths: the prompt carries the checkpoint's absolute path, so open it and read its Session ID and Task slug fields, then run --resolve-paths --session-id --task-slug to get back / / `.
- Is the work still alive? Check the heartbeat:
`` --heartbeat ` It prints {"alive": bool, "age_seconds": ...}`.
alive: true→ the main run is still going; exit quietly (it will handle its own pausing). Let the next tick re-check.alive: false(or checkpoint missing / marked complete) → proceed to take over. If the checkpoint is missing or its## Nextis empty,CronDeletethe job and exit — nothing to resume.
- Confirm the limit actually cleared. First block for a fresh render so you don't read a pre-reset file, then run the clear-check:
`` --wait-fresh --wait-timeout 20 --resume-check ``
{"status":"wait", ...}→ stop; the next retry tick will try again.{"status":"clear", ...}→ proceed.
- Re-establish context (a cron prompt fires in whatever cwd/branch the REPL holds):
cdto the checkpoint's recorded absolute cwd.git checkout. If the working tree has uncommitted WIP from a hard cut, commit it withgit add -Afollowed bygit commit -m "wip(otsukare): recovered uncommitted work"(two separate commands; PowerShell rejects&&). Then verify the base is sane; if the recorded WIP SHA is set, confirmgit rev-parse HEADis that SHA or a descendant of it. If the branch/state is clearly wrong, abort and report — do not continue on the wrong base.
- Reload the
## Nextlist from the checkpoint. - Record the resume in run metrics. `
is keyed by the **original**session_id` (re-resolved above from the checkpoint), so a dead-man's-switch takeover in a fresh session still updates the same metrics:
`` --state --state-action resume ``
- Notify (one line):
resuming , 5h now at X%. - Re-arm a fresh safety net for the new window (
--arm) and continue the work from## Next, re-entering this run loop.
On completion (summary + cleanup)
When the wrapped work finishes:
- Print the run summary — continues, run time, tokens, and cost, all measured as the delta over the otsukare span:
`` --state --state-action summary ` Show its output to the user verbatim, e.g.: ` お疲れ! otsukare summary — Continued: 2× (91%, 96%) Run time: 4h 18m total · 1h 51m active · 2h 27m waiting Tokens: 412K in/out · 4.1M cache (4.5M total) Cost: $3.84 ` (If the run never paused, Continued: 0×` — still a tidy receipt of time/tokens/cost.)
CronDeletethe safety-net job (using the ID in the checkpoint).- Mark the checkpoint done — delete `
or writeSTATUS: complete` at the top. - Remove the heartbeat and state file:
--cleanup --heartbeat --state.
A run that never hits a limit thus creates the safety-net job at start and deletes it at the end — net zero lingering jobs.
Notes & guards
- The data file updates only when the statusline renders; in practice that is every few seconds while the session is active. The
--wait-freshbarrier at start and resume blocks until a render lands with a current 5h window, so the initial reset time is never read from a stale file. For mid-run seam reads, the helper guards itself: usage within a window only rises, so an old-but-current-window file (stale: true) is handled by the stale buffer, while an expired-window file (stale_window: true,resets_at≤ now) has its pre-reset percentage discarded rather than buffered — so a read taken right after a reset can never force a false pause. Whenstale_windowis true, refresh via--wait-freshand re-read to get the new window's real usage. - Usage is account-global — it reflects whichever session rendered last. That is why resume mode requires the file to be fresher than the reset before trusting "cleared."
- If both 5h and 7d are over threshold, the helper binds to the later reset (you must wait for both).
- The safety net is the failsafe for a sudden swarm-to-100% that hard-cuts the session before a seam: it is armed up front, so a resume is scheduled even if the pause protocol never runs. The heartbeat ensures it only takes over when the main run is actually dead, never duplicating a live session.
- If there is no work left when a pause would trigger, just finish normally — run the cleanup, no resume scheduled.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: WillHsiaoNYC
- Source: WillHsiaoNYC/Claude-Code-Skill-Otsukare
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.