Function hooks: an event when session usage or a rate-limit window changes, so trackers do not have to poll
What we are building
Three mods that keep session figures on screen (https://github.com/Arunjay4213/claude-mods): the context window fill, the 5-hour and 7-day plan windows with a reset countdown and burn rate, and a per-turn cost ledger. All three read $.session.usage().
The gap
$.session.usage() is a pull. Nothing tells a mod that the figures moved, so a live meter has to sample on turn.complete and on a $.clock.every(60_000) tick. Two things follow:
- The countdown to a reset and a change in
percentUsedreach the screen up to a minute late. - Every idle session runs a timer that mostly finds nothing changed, which is exactly the kind of busy work the hooks worker's budgets exist to discourage.
In #91870 the same ask was raised as "an event when a limit changes state ({kind, percent, resetsAt})" and, after our mods landed, narrowed to "What remains of item 4 is an event when a threshold is crossed; today a quota guard has to poll" (https://github.com/anthropics/claude-code/issues/91870#issuecomment-5670691408). It has not been answered.
Proposal
A session.usage event dispatched by core whenever the figures $.session.usage() returns would change: after each API response that carries context or rateLimits, and when a window resets. Input as the same SessionUsage shape, so a hook can write:
on('session.usage', ($, e, next) => {
const five = e.rateLimits.find(r => r.kind === 'five_hour')
if (five && five.percentUsed >= 90) $.ui.toast('5-hour window at 90%')
return next(e)
})
With that event a tracker needs no timer at all. The 60-second tick would still be wanted for a countdown that moves while nothing else happens, but it could then do no I/O.
Refinements from discussion
From a second consumer of the same figures (budget-capped scheduled runs, polling the same way), folded in here so the proposal reads as one:
- Edge-triggered. Dispatch on the transition, not on a tick: a window can cross a threshold and reset inside one polling interval, and an event on the change fires exactly once.
- Coalesced per turn. A multi-call turn should produce one event with the last value, not one per API response.
- Carry the previous reading. A
previousfield with the priorSessionUsagelets a hook tell "just crossed 90%" from "already above 90%, still climbing" without keeping its own state. - Advisory, not a gate. The event surfaces the change; enforcement belongs at the call site with the real remaining budget, since aborting a run on the event races with in-flight calls.
Two smaller things seen on 2.1.270 while building this
rateLimits[].percentUsedarrives as floats such as7.000000000000001; whole numbers would match what the API means.- After a window resets,
rateLimitsdrops the window until the next API response. That is workable, but a note in the declarations would save the next person a surprise.
Claude Code 2.1.270, CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, Linux.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗