by ARahim3
A tiny Claude Code skill that keeps your prompt cache warm during idle sessions, so your next message reads from cache instead of paying full price.
# Add to your Claude Code skills
git clone https://github.com/ARahim3/cachebeatGuides for using cli tools skills like cachebeat.
Last scanned: 9/19/2026
{
"issues": [
{
"file": "SKILL.md",
"line": 38,
"type": "dangerous-command",
"message": "Dangerous command (writes to Claude config): \">/dev/null | head -1); [ -z \\\"$F\\\" ] && { DIR=\\\"$HOME/.claude/\"",
"severity": "medium"
}
],
"status": "PASSED",
"scannedAt": "2026-09-19T08:33:47.392Z",
"npmAuditRan": true,
"pipAuditRan": true,
"promptInjectionRan": true
}See how cachebeat compares with popular alternatives.
cachebeat is an open-source cli tools skill for AI coding assistants such as Claude Code, Codex CLI, and ChatGPT, built by ARahim3. A tiny Claude Code skill that keeps your prompt cache warm during idle sessions, so your next message reads from cache instead of paying full price. It has 66 GitHub stars.
Yes. cachebeat passed SkillsLLM's automated security scan — a dependency vulnerability audit plus prompt-injection heuristics — with no high-severity issues. You can read the full report in the Security Report section on this page.
Clone the repository with "git clone https://github.com/ARahim3/cachebeat" and add it to your Claude Code skills directory (see the Installation section above). cachebeat ships a SKILL.md manifest, so compatible agents can discover and load it automatically.
cachebeat is primarily written in Shell. It is open-source under ARahim3 on GitHub, so you can review or fork the full source.
Yes. SkillsLLM lists many other CLI Tools skills you can browse and compare side by side. Open the CLI Tools category from the badge at the top of this page, or use the Related Skills and comparison links further down to weigh cachebeat against similar tools.
No comments yet. Be the first to share your thoughts!
Top skills in this category by stars
⚠️ Third-Party Software Notice
This skill is third-party open-source software developed and hosted independently on GitHub. SkillsLLM is an informational directory and does not control or maintain the underlying repository.
Any security checks, ratings, or warnings displayed by SkillsLLM are automated and limited in scope. They do not constitute a security certification or guarantee that the software is safe, error-free, or free from malicious code, vulnerabilities, compromised dependencies, or prompt-injection risks.
Review the source code, permissions, dependencies, and configuration before installing or running any third-party skill. Use is at your own risk. To the maximum extent permitted by applicable law, SkillsLLM is not liable for losses arising from third-party software.
/cachebeat [minutes] [hours] arms the heartbeat; /cachebeat stop turns it off. There is nothing
for you to run. The heartbeat is the Stop hook in this skill's frontmatter: Claude Code registered it
when this skill loaded, and when this turn ends the hook reads these same arguments from the transcript.
minutes: idle threshold — how long the session may sit silent before a beat. Default 50,
clamped to 5–55 (past ~55 the 1-hour cache TTL can lapse before a beat lands).hours: auto-stop deadline. Default 8, clamped to 1–24 — the abandoned-session guard: every beat
costs a small cached-price request, so a forgotten heartbeat must end itself.stop (or off): disarm.Reply in ONE line and call no tools — arming: the idle threshold, the auto-stop time, and that
/cachebeat stop ends it sooner; stopping: that it is off. Nothing more.
If the user asks in plain words to start, change or stop the heartbeat, invoke this skill (Skill tool)
with the matching arguments: the hook only reads /cachebeat invocations.
A system reminder starting with [cachebeat] (on some Claude Code versions: "Stop hook blocking error …"
followed by the same text) comes from the hook. It is not the user, and it is not an error.
. and nothing else: no tools, no summary, no status. The
wake-up itself already re-read the conversation from cache, which is the whole point, and every token
you add is re-read by every later request. If a real task of yours is mid-flight, ignore the reminder
and carry on with the task instead.beat.sh yourself, and never "re-arm" after a beat: the next turn's Stop hook does that.Stop paying full price to say "I'm back" to your own Claude Code session.
A tiny Claude Code skill that keeps your prompt cache warm while a session sits idle — so your next message reads the conversation from cache instead of re-billing it from scratch. Anthropic bills cache reads at 0.1× the input rate (published ratio, every model) — an uncached return costs ~10× more.
This is not just an API-billing thing. On Pro/Max subscriptions the same accounting drains your 5-hour window and weekly usage limit: come back to a long session cold, and one "how's it going?" can eat a visible chunk of your quota that a warm cache would barely have touched.
It was born in a real session: a multi-day model fine-tune babysat by Claude — training runs that took hours, checkpoints landing overnight, a human who occasionally sleeps. Every time the session went quiet for longer than the cache window, the next "how's it going?" re-billed the entire conversation history at full input price. On a long, tool-heavy session that's hundreds of thousands of tokens, re-billed every single time you stepped away too long.
One inactivity-triggered heartbeat later, it didn't.
Claude is stateless: your whole conversation is re-sent to the API on every turn. Anthropic's prompt caching makes this affordable — cached input costs ~10 % of the normal rate — but the cache expires after a TTL (up to 1 hour on long-TTL sessions). The failure mode:
Do that a few times a day on a long-running session and the "idle tax" quietly becomes the biggest line in your bill — or the reason you hit your usage limit by mid-week.
/cachebeat arms a silent background watcher inside your session. It's an inactivity timer, not a
metronome: it fires only when the session has been truly silent for N minutes — any message, reply,
or answered background event already refreshed the cache and resets the clock. On firing, Claude
wakes, answers with a single ., and the cache TTL restarts. The next timer starts by itself.
Break-even math: going cold once costs as much as ten heartbeats. Come back to the session even once and the heartbeat has paid for itself many times over — and the bigger the session, the bigger the absolute savings (the 10× ratio is flat; the token count isn't).
It also kills itself after a deadline (default 8 h), so an abandoned session doesn't drip-bill forever.
Note (Oct 2026): Claude Code 2.1.285 put a lifetime on background shell commands (30 min by default, 2 h at most) and tells Claude not to restart one that hit the 2 h cap. The previous cachebeat ran as exactly such a command, so it was killed before its first 50-minute beat (or at 2 h on a busy session) and never came back (#1). cachebeat now runs as an
asyncRewakeStop hook declared in the skill itself: no lifetime cap, no re-arm tool call, no permission prompt, and one request per beat instead of two. Upgrading? Copy both files again (see Install), then run/cachebeatonce in each session you want covered. A session still running the old instructions stops at its next re-arm instead of looping.
User-level (all projects):
mkdir -p ~/.claude/skills/cachebeat
cp SKILL.md beat.sh ~/.claude/skills/cachebeat/
Or project-level: copy both files into <your-project>/.claude/skills/cachebeat/.
That's the whole setup — two small files: the skill, and the watcher script its hook runs. Then in any
Claude Code session, type /cachebeat and you're done. There is nothing to approve: hooks don't ask
for permission, and Claude never runs a command for it.
Requirements: a recent Claude Code (tested on 2.1.288) with hooks enabled — skill-frontmatter hooks and
asyncRewake are standard features, but an organization policy that disables hooks
(disableAllHooks, allowManagedHooksOnly) also disables cachebeat.
/cachebeat # fire after 50 min of inactivity, auto-stop after 8 h
/cachebeat 40 # custom idle threshold in minutes (clamped to 5–55)
/cachebeat 40 4 # custom threshold + auto-stop after 4 h
/cachebeat stop # stop it
You can also just ask Claude ("keep the cache warm while this trains") — it invokes the skill for you.
You don't have to take it on faith — you can watch the cache stay warm.
The heartbeat itself. When a beat fires you'll see a ⏺ cachebeat heartbeat line and Claude
answering . — proof the timer is alive and resetting the TTL. If instead Claude tells you
cachebeat stopped itself, the beat fired on schedule but the cache had still expired: your
session's TTL is too short for any heartbeat to bridge.
The status line. Claude Code's token readout looks like tok:312.0k/0.0k — total / served
from cache. When you return to an idle session, the second number should be a large fraction of
the first (warm). If it reads …/0.0k, the whole context was re-read uncached — the cache had
died. With cachebeat armed on a long-TTL session, it should stay warm across your idle gaps.
The transcript (exact numbers). Every assistant reply records what it cost. Run this inside the
session (prefix a shell command with !):
F=$(ls ~/.claude/projects/*/"$CLAUDE_CODE_SESSION_ID".jsonl 2>/dev/null | head -1)
echo "cache_read: $(tail -n 400 "$F" | grep -oE '"cache_read_input_tokens":[0-9]+' | tail -1 | grep -oE '[0-9]+')"
echo "cache_creation: $(tail -n 400 "$F" | grep -oE '"cache_creation_input_tokens":[0-9]+' | tail -1 | grep -oE '[0-9]+')"
A warm turn shows a big cache_read and a tiny cache_creation. A cold turn is the
reverse — a large cache_creation means the context was rebuilt from scratch at full price.
The skill's frontmatter declares a Stop hook with asyncRewake: true. Claude Code registers it when
you invoke /cachebeat, and from then on runs beat.sh in the background at the end of
every turn. Each run is one inactivity timer. If the hook exits with code 2, Claude Code wakes Claude
and shows it what the hook printed:
~/.claude/projects/<slug>/<session-id>.jsonl). A new response resets the clock...The heart of it:
while :; do
sleep 30
[ "$(cat "$S.pid")" = $$ ] || exit 0 # a newer turn started a newer timer
SIG=$(sig "$(last_resp)") # the last real model response
[ "$SIG" != "$PREV" ] && { PREV=$SIG; LAST=$NOW; } # new request -> reset the clock
IDLE=$((NOW - LAST))
[ "$IDLE" -ge $((N*60)) ] && { echo "idle heartbeat …" >&2; exit 2; } # exit 2 wakes Claude
done
| exit | meaning | what Claude does |
|---|---|---|
0 |
nothing to say: not armed, stopped, superseded, or the cache is already cold | nothing — no wake-up |
2 + "idle heartbeat" |
the session sat idle for the threshold | replies . (the reply's turn starts the next timer) |
2 + "stopped itself" |
the last beat still came back uncached — TTL too short to bridge | tells you, stays off |
2 + "auto-stop" |
the deadline passed | tells you, stays off |
Details that keep it honest:
/cachebeat in the transcript
(typed, or Claude's own Skill call) and keeps them, with the auto-stop deadline, in
$TMPDIR/cachebeat-<session-id>. Invoking /cachebeat again re-arms with the new values; /cachebeat stop disarms. The match is on raw JSON structure, so a transcript that merely quotes those lines
(say, while debugging cachebeat) can't arm it.queue-operation entries when a Workflow's agents finish background commands, mode,
away_summary, its own "model":"<synthetic>" notices for usage limits and API errors. Those don't
refresh the cache, so they don't reset the clock (they used to, and a busy Workflow could keep the
old watcher from ever firing while the cache died).cache_creation (or 5-minute-TTL cache writes) means the cache had already expired, so it stops
and tells you instead of pretending to help.SessionEnd hook: /clear, /resume and exit disarm the timer before
it can beat into the next conversation. If Claude Code dies outright, the timer notices its parent is
gone and ex