Function hooks: an event when session usage or a rate-limit window changes, so trackers do not have to poll

Status Open
Reported on v2.1.270
Maintainer reply None cached
Activity 2 comments · opened Sep 15, 2026

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:

  1. The countdown to a reset and a change in percentUsed reach the screen up to a minute late.
  2. 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 previous field with the prior SessionUsage lets 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[].percentUsed arrives as floats such as 7.000000000000001; whole numbers would match what the API means.
  • After a window resets, rateLimits drops 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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗