[FEATURE] Pass quotaLimits (rateLimitType, resetsAt) to the StopFailure hook input

Status Open
Reported on v2.1.284
Maintainer reply None cached
Activity 0 comments · opened Sep 28, 2026

Preflight Checklist

  • [x] I have searched existing requests and this feature hasn't been requested yet
  • [x] This is a single feature request (not multiple features)

Problem Statement

A StopFailure hook can't tell which usage limit ended the turn.

On 2.1.284 the StopFailure input is the base fields plus error, error_details? and last_assistant_message?. For a usage-limit 429, error is "rate_limit" whatever the limit is (five_hour, seven_day, seven_day_opus, seven_day_sonnet, seven_day_overage_included, overage). The kind appears only in the display text of last_assistant_message ("You've hit your session limit · resets 12am (Asia/Tokyo)", "weekly limit", "Opus limit", ...). So a hook has to match English UI copy, and gets the reset time only as a localized clock string.

The structured values already exist. The synthetic assistant message that Claude Code writes to the transcript for that 429 carries quotaLimits, for example (2.1.280, abridged; the other fields are overage and upgrade state):

"error": "rate_limit",
"quotaLimits": {
  "status": "rejected",
  "rateLimitType": "five_hour",
  "resetsAt": 1790175600,
  "lowPriorityRetryAfterSeconds": 20
}

They just aren't copied into the hook input.

Related: #94811 asks for a coarse error_class and retry_after_seconds on StopFailure. This request is narrower: pass through the quota fields Claude Code already has.

Proposed Solution

When the failing message has quotaLimits, add to the StopFailure input:

  • rate_limit_type: quotaLimits.rateLimitType
  • resets_at: quotaLimits.resetsAt (epoch seconds)

Both are absent when the stop was not a usage limit, so existing hooks are unaffected.

Alternative Solutions

  • Match the text of last_assistant_message. It breaks when the wording or locale changes, and new per-model limits need new patterns.
  • Read transcript_path and find the last isApiErrorMessage line. This means parsing the transcript JSONL from inside a hook, for two values Claude Code already has in memory.

Priority

Low - Nice to have

Feature Category

Developer tools/SDK

Use Case Example

A status display driven by hooks shows why a session stopped and what to do, because the right response differs by limit:

| rateLimitType | What the user does |
|---|---|
| five_hour | wait until resetsAt |
| seven_day | stop for the week |
| seven_day_opus / seven_day_sonnet / seven_day_overage_included | switch models (the message itself says so) |

With rate_limit_type and resets_at in the hook input, the display can show "session limit, resets 00:00" or "Opus limit, switch models" without parsing UI text.

Additional Context

  • Claude Code 2.1.284, Windows 11, CLI.
  • On this machine the main-session transcripts hold 20 usage-limit messages with quotaLimits (2.1.267 to 2.1.280), all five_hour. In all 20, resetsAt matched the reset time in the message text.

View original on GitHub ↗