• v1.13.0 6eabe4e165

    v1.13.0 — stop the monitor amplifying 429s on a shared Riot key
    All checks were successful
    CI / build-and-test (push) Successful in 2m28s
    CI / docker-build (push) Successful in 27s
    Stable

    joeyr released this 2026-08-25 19:09:27 -05:00 | 67 commits to main since this release

    The live monitor was taking sustained Riot 429s while polling 11 summoners once
    every 5 minutes — one call every ~27s, roughly 4% of a personal key's budget. It
    was never the heavy spender: the key is shared, and nothing here could see that.

    Added

    • Riot budget telemetry. Every Riot response carries X-App-Rate-Limit,
      X-App-Rate-Limit-Count, the method-scoped pair, and — on a 429 —
      X-Rate-Limit-Type. All of it was discarded, leaving Retry-After as the only
      evidence a 429 had happened, and no way at all to tell an application limit
      from a method one. RiotApiClientBase now reads them and logs a
      Riot budget nearly spent warning once the key-wide count reaches 80% of the
      cap, reporting mine (this process's dispatches) beside key (what Riot
      counted for the whole key). The gap between those two is what other holders
      of the key spent
      — the one thing that could not be observed from inside this
      process, and the reason a 4%-of-budget poller was seeing 429s at all.
    • RiotRateLimiterLike gained an optional getLongWindowCount() so the client
      can report its own spend without importing the concrete limiter.

    Changed

    • riotMaxRequestsPer2Min 75 → 40. The published personal-key cap is
      ~100/2min, but the same key is also held by the lol-companion project, whose
      limiter runs to its own ceiling knowing nothing of this one. Two independent
      limiters on one budget are safe only if their ceilings sum under the cap —
      these summed to 165. 40 here pairs with ~50 there.
    • Recent-form lookup drops from 5 matches to 3. The enrichment pass cost
      10 × (1 + 5) = 60 match calls, and with the ranked pass alongside it a single
      detected game spent ~70 requests of a shared 100 in one burst. At 3 the same
      pass costs 40. Recent form is a one-line hint, not a sample worth 60 requests.

    Fixed

    • A 429 no longer manufactures a second 429. The retry wait was
      Math.min(retryAfter, 10), so Riot asking for 17s produced a retry at 10s —
      while still rate-limited — turning one refused request into two and feeding the
      pressure that caused it. Production logs show precisely that at 09:26:02:
      retryAfter=17 logged as "retrying in 10s", refused again ten seconds later.
      The wait now honors what Riot asks for.
    • A long Retry-After skips the call rather than overrunning the poll cycle.
      Simply honoring an app-limit Retry-After of up to 120s across 3 retries could
      sleep ~6 minutes inside a 5-minute cycle, and pollOnce's in-progress guard
      would then drop the next cycle wholesale. Waits beyond 25s now abandon the
      call — the last staggered summoner starts ~27s before the cycle is due. A
      spectator answer describes right now, so a two-minute-old one is worthless when
      the next cycle re-asks in five minutes.
    • The header-less 429 fallback rose from 2s to 8s. Riot omits Retry-After on
      some service-level 429s, and 2s retried straight back into the same wall.
    • js-yaml pinned to ^5.4.0 via overrides, clearing GHSA-5p4m-2wfm-xmqj
      (High — quadratic CPU consumption in !!omap resolution), which the
      npm audit --audit-level=high CI gate step was failing on. Dev-only,
      reached solely via
      ts-jest → @jest/transform → babel-plugin-istanbul → @istanbuljs/load-nyc-config,
      so it was never in the production image (npm ci --omit=dev). The 3.x line
      never received the backport; the major bump is safe because load-nyc-config
      calls js-yaml's .load(), not the safeLoad() removed in 4.x.
    Downloads