add simulated 5h/7d usage limits via [rate_limits] config - #125
Merged
Conversation
Setups where Claude Code does not supply rate_limits in its stdin JSON
previously rendered the 5h bucket as infinity and omitted 7d entirely.
This adds an opt-in simulator so those setups can model the limits.
New [rate_limits] table takes inline five_hour / seven_day tables:
[rate_limits]
five_hour = { budget = 44_000_000, window = "5h", anchor = "rolling" }
seven_day = { budget = 440_000_000, window = "7d", anchor = "fixed", epoch = "0 0 * * 0" }
Defining a key synthesises that bucket and overrides any real value;
omitting it leaves existing behaviour untouched. used_percentage is
derived from trailing-window TickRecord history against budget, clamped
to 0-100, with a genuine resets_at so the burndown trend is not
suppressed. Rolling windows run now-window..now; fixed windows align to
a cron epoch.
cron.py implements a narrow stdlib-only 5-field subset ('*', integers,
comma lists, '*/n'); anything else is a load-time error, as are epoch
with anchor="rolling" and anchor="fixed" without epoch.
Derived values are quantized to the minute, so they are stable within a
minute and advance on rollover. A named regression guard pins the
interval at 60s so it cannot drift onto the shared 300s CACHE_TTL.
Also makes an idle 7d bucket render as infinity to match 5h, rather
than being omitted, for consistency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Extends the demo harness to model a Claude Code setup with no rate_limits payload at all, and a present-but-idle 7d bucket - closing the coverage gap PR #125 flagged as a follow-up (it needed a hand-crafted fixture since no scenario exercised the idle-7d path). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Demonstrates the PR's headline feature directly: stdin supplies no rate_limits payload, but a [rate_limits] config table is set, so YAS synthesizes real 5h/7d usage instead of showing infinity/omitting the bucket. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tmck-code
marked this pull request as ready for review
August 30, 2026 10:38
The new opt-in 5h/7d usage simulator had no entry in the example config, unlike every other config table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Claude Code only supplies
rate_limitsin its stdin JSON on some setups. Without it, YAS renderedthe 5h bucket as
∞and omitted the 7d bucket entirely, so users on those setups had no way to seeor reason about their usage windows.
This adds an opt-in simulator so a setup with no real limit data can model the 5h/7d windows
locally, and makes the two buckets behave consistently when idle.
Ships in v0.9.0.
Changes
New
[rate_limits]config tableInline
five_hour/seven_daytables withbudget,window,anchorandepoch:Defining a key synthesises that bucket and overrides any real payload value; omitting it
leaves existing behaviour untouched.
used_percentageis derived from trailing-windowTickRecordhistory againstbudget, clampedto 0-100.
resets_atis a genuine future timestamp, so the burndown trend isn't suppressed.anchor = "rolling"runsnow-window .. now;anchor = "fixed"aligns to the cronepoch.Cron subset (
claude/yas/cron.py)*, integers, comma lists and*/nsteps.epochwithanchor="rolling", andanchor="fixed"withoutepoch.Minute-granularity updates
CACHE_TTL_SECONDS.usage_sincescans withinmon's polling loop.(It cannot span statusline renders, since each render is a fresh process — the cross-render
stability comes from the quantization, not the cache.)
Consistency fix
∞like 5h, instead of being omitted. This is a change to defaultbehaviour, independent of the new config.
Checklist
Screenshots / recording
kitchen-sinkis the default demo fixture;idle-7dis a hand-crafted variant (no existing demoscenario has an idle
seven_daybucket — all 50 set a nonzeroseven_day_pct) confirming thatmainomits the idle 7d bucket while this branch renders∞. The new cell picks up a matching┬/┴divider pair and the right box edge stays aligned; the full demo gate over all 50 scenariosshowed border/elbow glyphs byte-identical (the only diffs were pre-existing ±1-cell sparkline
jitter that reproduces on
main).rate-limits-config-synthesizeddemonstrates this PR's headline feature end-to-end: stdin suppliesno
rate_limitspayload at all, but a[rate_limits]config table is set. Before (main) hasno config support and no stdin data, so it shows the exact bug described in Context (5h
∞, 7domitted). After (this branch), the config-driven simulator synthesizes real usage from local
history — 5h ≈ 53.3%, 7d ≈ 36.9% — instead of
∞/omitted.Benchmark
mainPRWithin noise — the error bars overlap.
System info
🤖 Generated with Claude Code