claude-code-limit-alerts
CLAUDE.md1 starsChanged 4 months agoMITView on GitHub
Pure bash tooling for macOS: monitors Claude Code subscription usage limits and sends notifications. No framework, test runner, or build step — the whole project lives in shell scripts installed into ~/.claude/. - .claude/ (this file, agents, skills, hooks) — English. This is operational documentation for Claude Code itself; English gives the best model performance. - docs/ (user-facing documentation, e.g. docs/how-it-works.md) — the project's user-facing languages, currently RU + EN (mirrors README.md/README.en.md). Keep both in sync when either changes. -…
- Installs packages
In this repository7 other files it gives its agents.
- check-scriptsSkill
- releaseSkill
- sync-readmesSkill
- doctorSkill
- setupSkill
- statusSkill
- uninstallSkill
The file
We split this CLAUDE.md into the rules it gives an agent
1 rule. About 0 are common, 1 is distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 113
Never add a new shared usage file without the same tag.
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
claude-code-limit-alerts
Pure bash tooling for macOS: monitors Claude Code subscription usage limits
and sends notifications. No framework, test runner, or build step — the whole
project lives in shell scripts installed into ~/.claude/.
Documentation language
.claude/(this file, agents, skills, hooks) — English. This is operational documentation for Claude Code itself; English gives the best model performance.docs/(user-facing documentation, e.g.docs/how-it-works.md) — the project's user-facing languages, currently RU + EN (mirrorsREADME.md/README.en.md). Keep both in sync when either changes.
Structure
install.sh/uninstall.sh/update.sh— manage files in~/.claude/scripts/, hooks in~/.claude/settings.json, and the launchd agent (~/Library/LaunchAgents/com.claude.usage-monitor.plist).lib/hooks.sh— shared hook-registration logic (add_hook,register_monitor_hooks,register_attention_hooks), sourced byinstall.shandupdate.sh; never copied into~/.claude/scripts. It does ship inside the plugin, though —source: "./"puts the whole repository,lib/included, into the plugin cache.lib/launchd.sh— shared launchd plist generation (generate_plist,print_proxy_status), including proxy-env passthrough for corporate proxy/VPN setups; sourced byinstall.shandupdate.shthe same way aslib/hooks.sh.scripts/usage-monitor.sh,scripts/notify-attention.sh,scripts/statusline-with-limits.sh,scripts/compact-advisor.sh— the files actually copied into~/.claude/scripts/and run by hooks/launchd/statusline.compact-advisor.shis aStophook;fanout-guard.shis aPreToolUsehook (matcherAgent|Task), off by default —install.shinstalls it only with--guard-fanout.- Don't reintroduce auto-resume. 0.5.0 removed
auto-resume.shbecause Claude Code does it natively ("Continue automatically at usage limit" in/config,autoContinueAtUsageLimitin~/.claude/settings.json). An external worker needs the session exited first and, alongside the built-in one, resumes one conversation twice. This project warns about limits; it does not manage sessions. launchd/com.claude.usage-monitor.plist.template— plist template.__LABEL__,__SCRIPT__,__ERRLOG__,__INTERVAL__(and legacy__HOME__) are substituted bygenerate_plist, whose 5th-8th parameters are optional — empty means the classic install's label, script and error log, and a 92-second interval. The interval is always passed explicitly (--interval/ theintervaloption), never carried over from an older plist: that cannot tell a user's choice from an old version's default.docs/how-it-works.md— data source (/api/oauth/usageendpoint, Keychain), hook logic, and anti-spam rules.docs/superpowers/plans/,docs/superpowers/specs/— plans and specs left behind by thesuperpowers:writing-plans/superpowers:brainstormingskills. Local working drafts, gitignored — not committed to the repository.
Plugin surface
The repository root is the plugin (source: "./"), so scripts/ is reused
from the same files with no copy.
.claude-plugin/plugin.json— manifest anduserConfig;.claude-plugin/marketplace.json— single-entry catalogue.hooks/hooks.json— Stop / SessionStart / Notification / PreToolUse. Every command passesUM_STATE_DIR="${CLAUDE_PLUGIN_DATA}"explicitly.skills/{status,setup,uninstall,doctor}/SKILL.md— thin triggers; logic stays in bash. (.claude/skills/is a different thing entirely — those are development skills for this repository and are not shipped to plugin users.)scripts/plugin-bootstrap.sh—SessionStarthook.- The one constraint to remember:
${CLAUDE_PLUGIN_ROOT}points into a version-stamped cache, so any path written outside the plugin (the statusline command, the launchd plist) must go through${CLAUDE_PLUGIN_DATA}/bin/instead. The bootstrap re-copies those scripts on a version change, but rewrites the generated wrappers on every session start — they carry the resolved options, which change whenever the user edits the configuration. - A bootstrap older than
bin/.versionmust not touch anything. A session keeps the hooks of the version it started with and re-runs its bootstrap on resume,/clearand compaction; letting it copy or rewrite made old and new sessions flip the mirror and the plist back and forth. TheSTALEguard skips mirroring, wrappers and every "apply" step; only removal (an option set tofalse) still runs. statuslineandlaunchdare tri-state, not boolean:trueapplies,falseremoves, unset is a no-op. The skills run the bootstrap from a plain shell whereCLAUDE_PLUGIN_OPTION_*is absent, and defaulting tofalsethere would destroy the statusline the user just enabled. Options resolve environment-first, then frompluginConfigsin~/.claude/settings.json.- Never add
CLAUDE_PLUGIN_DATAto the state-directory chain. It is exported by whichever plugin owns the running hook and leaks into unrelated shells; a classic install would silently relocate its state into another plugin's data directory.UM_STATE_DIRis the only knob. userConfigdefaults are never materialised into the environment — an unsetCLAUDE_PLUGIN_OPTION_*is the normal case, so every default is duplicated in bash. Booleans arrive as the literal stringstrue/false.- The statusline's stdin
rate_limitsfreeze while a session idles — Claude Code only refreshes them from its own API responses. That is why each window takes the fresher of stdin and the account-wide cache (pick_window: later reset wins, same window → higher percent; usage only grows within a window), and why the statusline starts a detachedusage-monitor.sh hookwhen the cache is stale. Never go back to "stdin always wins". Sessions share their freshest stdin reading throughusage-monitor-observed(mkdir lock + retry), and a live request runs underusage-monitor.fetch.lockso concurrent callers share one request. - Usage data is per account. The key is
accountUuid:organizationUuid(the user switches accounts; one person can hold a personal plan and a Team seat).usage-monitor.shtakes it from the Keychain token itself — one/api/oauth/profilerequest per token, remembered by token hash inusage-monitor-accounts.json— and falls back tooauthAccountin~/.claude.json; the statusline, fan-out guard and mod read~/.claude.jsononly. State and backoff are one file per account (*.<key>.json, key = first 8 chars of each); the cache andusage-monitor-observedcarry the account and are ignored for any other. An untagged cache is only a no-sign-in install's, and an unparsable~/.claude.jsonmeans "account unknown" — write nothing, trust no cache. Never add a new shared usage file without the same tag. CLAUDE_CONFIG_DIRmoves Claude Code's files, so ours follow. Every script reads${CLAUDE_CONFIG_DIR:-$HOME}/.claude.json(a non-empty<config dir>/.config.jsonwins),${CLAUDE_CONFIG_DIR:-$HOME/.claude}/…for settings, sessions and tasks, and the Keychain entryClaude Code-credentials-<sha256(dir)[0:8]>— the variable as given, not resolved, exactly as Claude Code computes it. Never hard-code$HOME/.claude.jsonor~/.claude/settings.jsonin a hook-side script. The launchd agent is the exception: one per macOS user, owned by the default configuration's plugin. A bootstrap under anotherCLAUDE_CONFIG_DIRleaves it alone (it only removes a plist pointing at its own data) — otherwise two installs rewrote the plist on every session start.install.shand its state stay on~/.claude.- Every hook script exits at once on
UM_INTERNAL=1.usage-monitor.shsets it on its headlessclaude -p /usagerefresh, which fires this project's own hooks; without the early exit each refresh made one more request to the usage endpoint. A new hook script needs the same line. CLAUDE_PLUGIN_OPTION_*leak just likeCLAUDE_PLUGIN_DATA— the names are generic, not per plugin. A script reads them only behind theFROM_PLUGINcheck (${BASH_SOURCE[0]}under${CLAUDE_PLUGIN_ROOT}), so a classic install never sees them. InpluginConfigslookups, test withhas(), never jq's//://treats a storedfalseas missing.- The plugin's launchd label is
com.claude.usage-monitor.plugin, deliberately distinct from the classic one, andUM_NO_LAUNCHCTL=1makes the bootstrap generate the plist without touching launchd (which ignoresHOME, so this is the only way to verify that path safely).
Experimental mod
mods/limit-alerts-status/— a separate plugin of TypeScript function hooks (a Claude Code "mod"), not part of thelimit-alertsplugin and not listed inmarketplace.json. It registers one command,/limits(immediate), answered by acommand.runhook with{ text }— no model turn, no tokens. 5h/7d take, per window, the fresher of$.session.usage().rateLimitsand the monitor's cache (freshest, the same rule as the statusline'spick_window; same directory chain as the bash scripts, then~/.claude/plugins/data/limit-alerts-*); the scoped weekly limit always from the cache; the 429 backoff fromusage-monitor-backoff.json. Local time comes from the host'sdate +%z— the worker's own zone is not to be trusted.- It used to pin a permanent
$.ui.statusrow. Dropped on purpose: the engine styles that row as a notice (yellow▲, plugin-name prefix, no colour API), and since 0.7.0 the statusline shows the same live numbers in colour. Don't bring the row back as a duplicate. - The mod API is early access and changes between Claude Code releases —
that is why it stays out of the shipped plugin. Load it with
claude --plugin-dir mods/limit-alerts-status; check it withclaude plugin validateandclaude plugin teston that folder..claude-plugin/types/inside it is generated by the engine and gitignored. - It is the one place in the repository that is not bash; the "no test
framework" rule below covers the shell scripts, the mod has its own
*.test.ts.
Conventions
- macOS only. Scripts may rely on
launchctl,security,osascript,afplay. Linux/Windows are on the roadmap but not supported yet — don't add cross-platform shims unless that's the actual task. set -euo pipefailat the top of every executable script — never remove it.jqis required — resolved viaresolve_jq()inlib/hooks.sh; if missing, a script must fail with a clear message (jq is required. Install it with: brew install jq), not continue silently.- install/update idempotency.
install.sh,update.sh, andadd_hook()must be safe to re-run — no duplicate hooks insettings.json, no duplicate launchd registrations.update.shrespects the flags used at install time (--no-statusline/--no-launchd/--no-attention) — don't enable something that wasn't installed. - The user's
~/.claude/settings.jsonis someone else's file. Any edit frominstall.shmust be preceded by a backup; existing hooks and statusline are preserved, never overwritten. - No test framework. Verification is manual, with real commands and
expected output, plus
bash -nfor syntax. When manually verifying steps that touch~/.claude/settings.jsonor the real launchd agent, do it with an overriddenHOME(sandboxed), never against the user's real environment. - Versioning:
VERSION(repo root) is the source of truth,CHANGELOG.mdfollows Keep a Changelog- SemVer. Every behavioral change in
install.sh/uninstall.sh/update.sh/lib//scripts/gets its ownCHANGELOG.mdentry and, if warranted, aVERSIONbump. See thereleaseskill.
- SemVer. Every behavioral change in
- README is bilingual.
README.md(Russian, primary) andREADME.en.md(English) must stay structurally in sync — same sections, same order. See thesync-readmesskill. - Commits: conventional-style prefixes (
feat:,fix:,refactor:,chore:,docs:) — matches the existing project history.
Before committing
Skill check-scripts — bash -n (+ shellcheck, if installed) over every
changed *.sh.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

