Purpose
Narrow-write setup. This plugin's configuration surface is three kinds of thing setup cannot
conformingly write:
- A system tool (
jq). check probes it; installing it is the operator's.
- One native
userConfig toggle (rate_limit_guard_enabled), whose only stored home is the
pluginConfigs setup must never write. Reconfigure through Claude Code's native flow, per the
marketplace's plugin-reconfiguration convention
(https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md,
which owns the verified-version record): interactive /plugin configure rate-limit-guard@<marketplace> any time, or headless claude plugin install rate-limit-guard@<marketplace> -s <scope> --config rate_limit_guard_enabled=<value> (repeatable
per key) — against an already-installed plugin it prints already installed and still writes
the value. Do not uninstall to reconfigure: that drops this plugin's entire stored
pluginConfigs entry, resetting every option in the README's Options reference to its manifest
default. -s defaults to user; pass the scope claude plugin list reports, and run from that
project's directory for a project/local scope, or the write lands at a scope that does not
load. Afterwards rerun check in a fresh session — the rendered ${user_config.*} is
injected at skill load and each hook's CLAUDE_PLUGIN_OPTION_* is fixed at session start, so a
same-session check still reports the old value; report the observed effective value, never an
unobserved change.
- The statusline wiring, which lives in the user's own
settings.json, neither
userConfig nor tracked project config, and a Claude Code settings surface setup must never
mutate.
So check inspects, reports PASS/FAIL/INFO with one remediation line per FAIL, and prints the
exact statusline edit for the operator to apply by hand, fully resolved, marked as the
operator's, and naming what re-invalidates it. Silence would not be the conforming response on an
unwritable surface; a printed edit is.
What obliges an apply is not configuration at all. The tee's and the hook's machine files under
~/.claude/rate-limit-guard/ remain runtime-owned plugin data, not an operator-editable surface,
but the statusline shim ~/.claude/rate-limit-guard/bin/statusline-shim.sh is an owned
writable artifact this plugin must place, because it is the durable path the operator's own wiring
names. apply writes that one file and nothing else.
Why the shim exists (the durable-wiring rule). ${CLAUDE_PLUGIN_ROOT} is version-pinned and
changes on every plugin update, and the old version directory is pruned about 14 days later
(plugins reference, "Plugin cache and file access"). A statusline wired straight to
<plugin-root>/scripts/statusline-tee.sh therefore stops teeing at the next version bump and, once
the old directory is pruned, bash <missing-path> exits 127 and takes the operator's whole
statusline down with it. So the operator wires the shim, never the tee: the shim lives at a
path that never changes, resolves the newest installed tee at run time, and degrades to running the
wrapped command alone when no tee is installed.
The scripts are the source of truth for their own behavior. Read
${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh,
${CLAUDE_PLUGIN_ROOT}/scripts/statusline-tee.sh and
${CLAUDE_PLUGIN_ROOT}/hooks/record-rate-limit-stop.sh first; probe what they actually do rather
than reciting this file. The consumer-facing constants (tee path, threshold, staleness rule) are
owned by ${CLAUDE_PLUGIN_ROOT}/reference/reader-contract.md.
check (read-only)
jq. command -v jq. FAIL if absent: without it the wrapper cannot tee (it stays
transparent and shows a visible notice) and the standalone statusline degrades. Remediation:
install jq (https://jqlang.org/download/).
Installed shim state. The shim is the wiring target, so check it before the wiring. Compare
~/.claude/rate-limit-guard/bin/statusline-shim.sh (the durable shim copy) against
${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh (the shipped source) and classify per
reference/legacy-statusline-detect.md "Installed shim
state", shared with the sibling guard plugin and synced byte-identical.
Statusline wiring state. Read (never write) every settings scope that can carry a
statusLine (user ~/.claude/settings.json, project .claude/settings.json, local
.claude/settings.local.json) and determine which one owns the effective command (the most
specific scope wins). All wiring states below are evaluated against that effective command,
and the printed edit in step 6 targets that scope's file. Wiring the user file while a
project-level statusLine shadows it would apply cleanly and never run; when a shadow
exists, say so explicitly and print the edit for the shadowing file (or note that removing
the override is the alternative). Distinguish four states:
- No
statusLine configured. The wrapper is not running because nothing is. Print the
standalone wiring from the template below (the shim is then the whole statusline).
statusLine present, command references neither the shim nor statusline-tee.sh.
wrapper missing. Print the wrapped wiring below with the user's current command preserved as
the wrapped command.
statusLine references a rate-limit-guard statusline-tee.sh under the plugin cache.
Legacy version-pinned wiring: classify, report, and remediate per
reference/legacy-statusline-detect.md "Legacy
version-pinned wiring" (the fix's apply is step 2's).
statusLine invokes ~/.claude/rate-limit-guard/bin/statusline-shim.sh. PASS. No path
comparison against ${CLAUDE_PLUGIN_ROOT} applies or is meaningful here; the shim resolves
the tee at run time.
Tee freshness. Probe the fixed contract path ~/.claude/rate-limit-guard/rate-limits.json:
jq -e '.rate_limits and .captured_at' passes and captured_at is within the staleness
window the reader contract's operable floor fixes (read the value there, never from here;
a number restated in this file is a copy nothing keeps in step with the contract) → PASS
(proactive mode available).
- File fresh but
rate_limits absent → INFO: this session's auth exposes no subscription
windows (API-key or enterprise auth); consumers correctly run reactive-only. Not a defect.
- File absent or stale while the wiring in step 3 looked correct → FAIL: the wrapper is wired
but not running (statusline refreshes only in interactive sessions; also re-check steps 2 and
3, because a shim that is wired but not installed produces exactly this). Note the file only updates
while some interactive session is active.
StopFailure hook. INFO: the hook needs no wiring (it registers via the plugin's
hooks/hooks.json); confirm the plugin is enabled (/plugin → Installed) and report the
effective kill switch ${user_config.rate_limit_guard_enabled} (unexpanded or empty means the
default true). Report whether ~/.claude/rate-limit-guard/stop-events.jsonl exists. Absent
just means no rate-limit stop has been recorded yet.
Print the operator edit. Always print the applicable settings.json statusline edit,
marked clearly as the operator's to apply. The wiring target is the shim's fixed path, never
${CLAUDE_PLUGIN_ROOT}, which is version-pinned and belongs in no operator file.
Compose the value by running
bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh" over the effective
statusLine value from step 3, never by peeling and wrapping the string yourself:
jq '.statusLine' <the settings file that owns the effective command> |
bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh" \
--wrap 'bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh' --block --explain
Read reference/unwrap-before-compose.md for that
script's argument and exit-code contract and the judgments it leaves to you, shared
byte-identical with context-guard. Composing by hand double-wraps a sibling tee and stacks
another sh -c layer on every re-run. The blocks below are this plugin's printed paths; the
script emits whichever one the current value selects, and its angle-bracket placeholders show
each form's shape. The script fills them; substituting into one by hand is the arithmetic it
exists to replace.
Wrapping an existing statusline command (the script substitutes the operator's own renderer,
recovered by the peel, as the trailing arguments):
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh <current statusline command>"
}
}
No statusline configured (standalone minimal statusline):
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh"
}
}
When the script selects the shell-wrapped form, <escaped original command> is already escaped
and JSON-escaped in its output and needs no further editing:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh sh -c '<escaped original command>'"
}
}
Sibling tees compose by nesting, each through its own shim. The tees are transparent wrappers,
so the innermost command still owns stdout and the exit code. Print this form (its tee outermost,
matching that plugin's setup skill) only when context-guard is installed and its shim is
already present at ~/.claude/context-guard/bin/statusline-shim.sh. The sibling shim is written
by /context-guard:setup apply, which the operator may not have run yet. Naming a path that
does not exist reintroduces exactly the failure this wiring exists to remove, because bash <missing-path> exits 127 before the operator's renderer ever runs. When the sibling plugin is
installed but its shim is absent, print the single-shim form above and say that
/context-guard:setup apply followed by a re-run of this check yields the combined wiring:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/context-guard/bin/statusline-shim.sh bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh <current statusline command>"
}
}
The combined form is one invocation, not a second transform: pass both shims as --wrap
prefixes in the order they nest, context-guard's first.
jq '.statusLine' <the settings file that owns the effective command> |
bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh" \
--wrap 'bash ~/.claude/context-guard/bin/statusline-shim.sh' \
--wrap 'bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh' --block --explain
Naming only one shim drops the other, because the peel strips every shim prefix it finds. The
shim paths are the only part that nests; whether the innermost command takes an sh -c adapter
is the same decision the script already made:
{
"statusLine": {
"type": "command",
"command": "bash ~/.claude/context-guard/bin/statusline-shim.sh bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh sh -c '<escaped original command>'"
}
}
State the measured cost with the combined form: each tee adds roughly 0.6–0.9 s per statusline
refresh on Windows/Git Bash (process-spawn bound), on top of the operator's own statusline
command.
refreshInterval sets how often that runs; the statusline is not on the input path, so the
cost is display latency, not typing latency.
Windows note: the command must run under Git Bash. bash is invoked explicitly for exactly
that reason (the script's stated shell requirement); with Git Bash absent Claude Code routes
statusline commands through PowerShell and this wiring does not apply (statusline reference,
"Windows configuration"). State this with the printed edit: the wiring is applied once and
survives every later plugin update, because the shim, not the version-pinned cache path, is
what the settings file names.
Dotfiles tracking proposal. The printed edit changes a durable user-scope file the operator
maintains. When the operator's home directory is managed by a dotfiles system (chezmoi, yadm, a
bare-repo setup, ...), surface the reminder to capture the settings.json change through that
system's own add/track flow so the wiring survives machine rebuilds. This skill only surfaces
the reminder; it runs no dotfiles command.
apply (writes ONLY the shim, on explicit request)
Copy ${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh to
~/.claude/rate-limit-guard/bin/statusline-shim.sh, creating bin/ if needed, and chmod +x the
result (a no-op on Windows ACL volumes; the wiring invokes it through bash anyway):
- The installed copy is byte-identical to the shipped source, never a rewrite, never a
templated variant. That is what makes
check step 2 a plain cmp.
- Idempotent: if the file already exists and compares equal, write nothing and say so.
Otherwise overwrite it (this is the update path after a plugin version bump changes the shim)
and report the
# shim-revision: values, old → new.
- The shim is inert until wired: installing it starts nothing. Only the operator's
settings.json edit, step 6 of check, which this skill never applies, puts it on the
statusline path. Say that explicitly when reporting the write.
- After installing, print the wiring edit (
check step 6) so the operator's next action is in
front of them, and note that a statusline already wired to the shim needs no change now or on
any future plugin update.
apply never touches settings.json, rate-limits.json, stop-events.jsonl, or anything outside
~/.claude/rate-limit-guard/bin/.
Uninstalling
Uninstalling the plugin removes the cache directory, not the operator's files. Nothing breaks: the
shim finds no tee and passes the wrapped statusline through unchanged (a wired-standalone shim
prints one notice line instead). Two operator cleanup steps remain, and their order matters.
Report both together, in this order, when asked how to back this out:
- Unwrap the
statusLine command first, restoring the operator's own renderer (or removing
the field entirely if the shim was the whole statusline).
- Then remove
~/.claude/rate-limit-guard/.
Deleting the directory while the wiring still names the shim leaves settings.json invoking a
missing file: bash <missing-path> exits 127 and takes the whole statusline down, the exact
failure the shim exists to prevent. The shim's own no-tee fallback cannot cover this, because the
fallback lives in the file that was just deleted.
What this skill does NOT do
- Write the plugin cache, Claude Code user settings, or
pluginConfigs, per the uniform setup
contract (docs/PLUGIN-PHILOSOPHY.md "Setup is explicit and repeatable" in the marketplace
repository). Nor settings.json (user or project) or any other Claude Code settings surface;
the printed edit is the operator's to apply.
- Install
jq or any system package.
- Write to the contract files. The wrapper and the hook own
rate-limits.json and
stop-events.jsonl; apply owns only bin/statusline-shim.sh.
- Write anywhere outside
~/.claude/rate-limit-guard/, including the sibling context-guard
directory, whose own setup skill installs that plugin's shim.
1---2name: setup-503description: Verify the rate-limit-guard plugin's wiring on this machine: jq, the installed statusline shim, statusline wiring (including legacy version-pinned plugin-cache paths), tee freshness, and the StopFailure hook. Print the exact statusline edit for the operator to apply, and install the statusline shim. Use when: 'set up rate-limit-guard', 'is the rate-limit tee working', 'wire the rate-limit statusline', the tee file is stale, or a consuming loop lane reports guard mode unknown. Actions: check (read-only; never edits settings), apply (writes ONLY ~/.claude/rate-limit-guard/bin/statusline-shim.sh, on explicit request).4---56## Purpose78Narrow-write setup. This plugin's **configuration** surface is three kinds of thing setup cannot9conformingly write:1011- **A system tool** (`jq`). `check` probes it; installing it is the operator's.12- **One native `userConfig` toggle** (`rate_limit_guard_enabled`), whose only stored home is the13 `pluginConfigs` setup must never write. Reconfigure through Claude Code's native flow, per the14 marketplace's plugin-reconfiguration convention15 (<https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/plugin-reconfiguration/README.md>,16 which owns the verified-version record): interactive `/plugin configure17 rate-limit-guard@<marketplace>` any time, or headless `claude plugin install18 rate-limit-guard@<marketplace> -s <scope> --config rate_limit_guard_enabled=<value>` (repeatable19 per key) — against an already-installed plugin it prints `already installed` and still writes20 the value. Do **not** uninstall to reconfigure: that drops this plugin's entire stored21 `pluginConfigs` entry, resetting every option in the README's Options reference to its manifest22 default. `-s` defaults to `user`; pass the scope `claude plugin list` reports, and run from that23 project's directory for a `project`/`local` scope, or the write lands at a scope that does not24 load. Afterwards rerun `check` in a **fresh session** — the rendered `${user_config.*}` is25 injected at skill load and each hook's `CLAUDE_PLUGIN_OPTION_*` is fixed at session start, so a26 same-session `check` still reports the old value; report the observed effective value, never an27 unobserved change.28- **The statusline wiring**, which lives in the **user's own** `settings.json`, neither29 `userConfig` nor tracked project config, and a Claude Code settings surface setup must never30 mutate.3132So `check` inspects, reports PASS/FAIL/INFO with one remediation line per FAIL, and **prints the33exact statusline edit for the operator to apply by hand**, fully resolved, marked as the34operator's, and naming what re-invalidates it. Silence would not be the conforming response on an35unwritable surface; a printed edit is.3637What obliges an `apply` is not configuration at all. The tee's and the hook's machine files under38`~/.claude/rate-limit-guard/` remain runtime-owned plugin data, not an operator-editable surface,39but the **statusline shim** `~/.claude/rate-limit-guard/bin/statusline-shim.sh` is an owned40writable artifact this plugin must place, because it is the durable path the operator's own wiring41names. `apply` writes that one file and nothing else.4243**Why the shim exists (the durable-wiring rule).** `${CLAUDE_PLUGIN_ROOT}` is version-pinned and44changes on every plugin update, and the old version directory is pruned about 14 days later45(plugins reference, "Plugin cache and file access"). A statusline wired straight to46`<plugin-root>/scripts/statusline-tee.sh` therefore stops teeing at the next version bump and, once47the old directory is pruned, `bash <missing-path>` exits 127 and takes the operator's whole48statusline down with it. So the operator wires the **shim**, never the tee: the shim lives at a49path that never changes, resolves the newest installed tee at run time, and degrades to running the50wrapped command alone when no tee is installed.5152The scripts are the source of truth for their own behavior. Read53`${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh`,54`${CLAUDE_PLUGIN_ROOT}/scripts/statusline-tee.sh` and55`${CLAUDE_PLUGIN_ROOT}/hooks/record-rate-limit-stop.sh` first; probe what they actually do rather56than reciting this file. The consumer-facing constants (tee path, threshold, staleness rule) are57owned by `${CLAUDE_PLUGIN_ROOT}/reference/reader-contract.md`.5859## `check` (read-only)60611. **`jq`.** `command -v jq`. FAIL if absent: without it the wrapper cannot tee (it stays62 transparent and shows a visible notice) and the standalone statusline degrades. Remediation:63 install jq (<https://jqlang.org/download/>).642. **Installed shim state.** The shim is the wiring target, so check it before the wiring. Compare65 `~/.claude/rate-limit-guard/bin/statusline-shim.sh` (the durable shim copy) against66 `${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh` (the shipped source) and classify per67 [reference/legacy-statusline-detect.md](reference/legacy-statusline-detect.md) "Installed shim68 state", shared with the sibling guard plugin and synced byte-identical.693. **Statusline wiring state.** Read (never write) every settings scope that can carry a70 `statusLine` (user `~/.claude/settings.json`, project `.claude/settings.json`, local71 `.claude/settings.local.json`) and determine which one owns the effective command (the most72 specific scope wins). All wiring states below are evaluated against that effective command,73 and the printed edit in step 6 targets that scope's file. Wiring the user file while a74 project-level `statusLine` shadows it would apply cleanly and never run; when a shadow75 exists, say so explicitly and print the edit for the shadowing file (or note that removing76 the override is the alternative). Distinguish four states:77 - **No `statusLine` configured.** The wrapper is not running because nothing is. Print the78 standalone wiring from the template below (the shim is then the whole statusline).79 - **`statusLine` present, command references neither the shim nor `statusline-tee.sh`.**80 wrapper missing. Print the wrapped wiring below with the user's current command preserved as81 the wrapped command.82 - **`statusLine` references a `rate-limit-guard` `statusline-tee.sh` under the plugin cache.**83 Legacy version-pinned wiring: classify, report, and remediate per84 [reference/legacy-statusline-detect.md](reference/legacy-statusline-detect.md) "Legacy85 version-pinned wiring" (the fix's `apply` is step 2's).86 - **`statusLine` invokes `~/.claude/rate-limit-guard/bin/statusline-shim.sh`.** PASS. No path87 comparison against `${CLAUDE_PLUGIN_ROOT}` applies or is meaningful here; the shim resolves88 the tee at run time.894. **Tee freshness.** Probe the fixed contract path `~/.claude/rate-limit-guard/rate-limits.json`:90 - `jq -e '.rate_limits and .captured_at'` passes and `captured_at` is within the staleness91 window the reader contract's operable floor fixes (read the value there, never from here;92 a number restated in this file is a copy nothing keeps in step with the contract) → PASS93 (proactive mode available).94 - File fresh but `rate_limits` absent → INFO: this session's auth exposes no subscription95 windows (API-key or enterprise auth); consumers correctly run reactive-only. Not a defect.96 - File absent or stale while the wiring in step 3 looked correct → FAIL: the wrapper is wired97 but not running (statusline refreshes only in interactive sessions; also re-check steps 2 and98 3, because a shim that is wired but not installed produces exactly this). Note the file only updates99 while some interactive session is active.1005. **StopFailure hook.** INFO: the hook needs no wiring (it registers via the plugin's101 `hooks/hooks.json`); confirm the plugin is enabled (`/plugin` → Installed) and report the102 effective kill switch `${user_config.rate_limit_guard_enabled}` (unexpanded or empty means the103 default `true`). Report whether `~/.claude/rate-limit-guard/stop-events.jsonl` exists. Absent104 just means no rate-limit stop has been recorded yet.1056. **Print the operator edit.** Always print the applicable `settings.json` statusline edit,106 marked clearly as the operator's to apply. The wiring target is the shim's fixed path, never107 `${CLAUDE_PLUGIN_ROOT}`, which is version-pinned and belongs in no operator file.108109 Compose the value by running110 `bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh"` over the effective111 `statusLine` value from step 3, never by peeling and wrapping the string yourself:112113 ```bash114 jq '.statusLine' <the settings file that owns the effective command> |115 bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh" \116 --wrap 'bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh' --block --explain117 ```118119 Read [`reference/unwrap-before-compose.md`](reference/unwrap-before-compose.md) for that120 script's argument and exit-code contract and the judgments it leaves to you, shared121 byte-identical with context-guard. Composing by hand double-wraps a sibling tee and stacks122 another `sh -c` layer on every re-run. The blocks below are this plugin's printed paths; the123 script emits whichever one the current value selects, and its angle-bracket placeholders show124 each form's shape. The script fills them; substituting into one by hand is the arithmetic it125 exists to replace.126127 Wrapping an existing statusline command (the script substitutes the operator's own renderer,128 recovered by the peel, as the trailing arguments):129130 ```json131 {132 "statusLine": {133 "type": "command",134 "command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh <current statusline command>"135 }136 }137 ```138139 No statusline configured (standalone minimal statusline):140141 ```json142 {143 "statusLine": {144 "type": "command",145 "command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh"146 }147 }148 ```149150 When the script selects the shell-wrapped form, `<escaped original command>` is already escaped151 and JSON-escaped in its output and needs no further editing:152153 ```json154 {155 "statusLine": {156 "type": "command",157 "command": "bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh sh -c '<escaped original command>'"158 }159 }160 ```161162 Sibling tees compose by nesting, each through its own shim. The tees are transparent wrappers,163 so the innermost command still owns stdout and the exit code. Print this form (its tee outermost,164 matching that plugin's setup skill) only when `context-guard` is installed and its shim is165 already present at `~/.claude/context-guard/bin/statusline-shim.sh`. The sibling shim is written166 by `/context-guard:setup apply`, which the operator may not have run yet. Naming a path that167 does not exist reintroduces exactly the failure this wiring exists to remove, because `bash168 <missing-path>` exits 127 before the operator's renderer ever runs. When the sibling plugin is169 installed but its shim is absent, print the single-shim form above and say that170 `/context-guard:setup apply` followed by a re-run of this check yields the combined wiring:171172 ```json173 {174 "statusLine": {175 "type": "command",176 "command": "bash ~/.claude/context-guard/bin/statusline-shim.sh bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh <current statusline command>"177 }178 }179 ```180181 The combined form is one invocation, not a second transform: pass both shims as `--wrap`182 prefixes in the order they nest, context-guard's first.183184 ```bash185 jq '.statusLine' <the settings file that owns the effective command> |186 bash "${CLAUDE_PLUGIN_ROOT}/scripts/compose-statusline-wiring.sh" \187 --wrap 'bash ~/.claude/context-guard/bin/statusline-shim.sh' \188 --wrap 'bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh' --block --explain189 ```190191 Naming only one shim drops the other, because the peel strips every shim prefix it finds. The192 shim paths are the only part that nests; whether the innermost command takes an `sh -c` adapter193 is the same decision the script already made:194195 ```json196 {197 "statusLine": {198 "type": "command",199 "command": "bash ~/.claude/context-guard/bin/statusline-shim.sh bash ~/.claude/rate-limit-guard/bin/statusline-shim.sh sh -c '<escaped original command>'"200 }201 }202 ```203204 State the measured cost with the combined form: each tee adds roughly 0.6–0.9 s per statusline205 refresh on Windows/Git Bash (process-spawn bound), on top of the operator's own statusline206 command.207 `refreshInterval` sets how often that runs; the statusline is not on the input path, so the208 cost is display latency, not typing latency.209210 Windows note: the command must run under Git Bash. `bash` is invoked explicitly for exactly211 that reason (the script's stated shell requirement); with Git Bash absent Claude Code routes212 statusline commands through PowerShell and this wiring does not apply (statusline reference,213 "Windows configuration"). State this with the printed edit: the wiring is applied once and214 survives every later plugin update, because the shim, not the version-pinned cache path, is215 what the settings file names.2167. **Dotfiles tracking proposal.** The printed edit changes a durable user-scope file the operator217 maintains. When the operator's home directory is managed by a dotfiles system (chezmoi, yadm, a218 bare-repo setup, ...), surface the reminder to capture the `settings.json` change through that219 system's own add/track flow so the wiring survives machine rebuilds. This skill only surfaces220 the reminder; it runs no dotfiles command.221222## `apply` (writes ONLY the shim, on explicit request)223224Copy `${CLAUDE_PLUGIN_ROOT}/scripts/statusline-shim.sh` to225`~/.claude/rate-limit-guard/bin/statusline-shim.sh`, creating `bin/` if needed, and `chmod +x` the226result (a no-op on Windows ACL volumes; the wiring invokes it through `bash` anyway):227228- The installed copy is **byte-identical** to the shipped source, never a rewrite, never a229 templated variant. That is what makes `check` step 2 a plain `cmp`.230- **Idempotent**: if the file already exists and compares equal, write nothing and say so.231 Otherwise overwrite it (this is the update path after a plugin version bump changes the shim)232 and report the `# shim-revision:` values, old → new.233- The shim is **inert until wired**: installing it starts nothing. Only the operator's234 `settings.json` edit, step 6 of `check`, which this skill never applies, puts it on the235 statusline path. Say that explicitly when reporting the write.236- After installing, print the wiring edit (`check` step 6) so the operator's next action is in237 front of them, and note that a statusline already wired to the shim needs no change now or on238 any future plugin update.239240`apply` never touches `settings.json`, `rate-limits.json`, `stop-events.jsonl`, or anything outside241`~/.claude/rate-limit-guard/bin/`.242243## Uninstalling244245Uninstalling the plugin removes the cache directory, not the operator's files. Nothing breaks: the246shim finds no tee and passes the wrapped statusline through unchanged (a wired-standalone shim247prints one notice line instead). Two operator cleanup steps remain, and their order matters.248Report both together, in this order, when asked how to back this out:2492501. **Unwrap the `statusLine` command first**, restoring the operator's own renderer (or removing251 the field entirely if the shim was the whole statusline).2522. **Then remove `~/.claude/rate-limit-guard/`.**253254Deleting the directory while the wiring still names the shim leaves `settings.json` invoking a255missing file: `bash <missing-path>` exits 127 and takes the whole statusline down, the exact256failure the shim exists to prevent. The shim's own no-tee fallback cannot cover this, because the257fallback lives in the file that was just deleted.258259## What this skill does NOT do260261- Write the plugin cache, Claude Code user settings, or `pluginConfigs`, per the uniform setup262 contract (`docs/PLUGIN-PHILOSOPHY.md` "Setup is explicit and repeatable" in the marketplace263 repository). Nor `settings.json` (user or project) or any other Claude Code settings surface;264 the printed edit is the operator's to apply.265- Install `jq` or any system package.266- Write to the contract files. The wrapper and the hook own `rate-limits.json` and267 `stop-events.jsonl`; `apply` owns only `bin/statusline-shim.sh`.268- Write anywhere outside `~/.claude/rate-limit-guard/`, including the sibling `context-guard`269 directory, whose own setup skill installs that plugin's shim.