From 0fdc9e66a60bb963dfcc73776f82018a4e5d1950 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Sun, 1 Nov 2026 12:10:54 -0500 Subject: [PATCH] fix(shared): isolate rate limiter reset for empty keys --- tests/shared-rate-empty-key.test.mjs | 26 +++++++++++++++++++ .../shared/src/resilience/rate-limiter.ts | 4 +-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 tests/shared-rate-empty-key.test.mjs diff --git a/tests/shared-rate-empty-key.test.mjs b/tests/shared-rate-empty-key.test.mjs new file mode 100644 index 0000000000..0e09d2e665 --- /dev/null +++ b/tests/shared-rate-empty-key.test.mjs @@ -0,0 +1,26 @@ +import { test } from 'vitest'; +import assert from 'node:assert/strict'; +import { SlidingWindowRateLimiter, TokenBucketRateLimiter } from '../v3/@claude-flow/shared/src/resilience/rate-limiter.ts'; + +for (const Type of [SlidingWindowRateLimiter, TokenBucketRateLimiter]) { + test(`${Type.name} resets only the explicit empty key`, () => { + const limiter = new Type({ maxRequests: 1, windowMs: 60_000 }); + try { + assert.equal(limiter.consume('').allowed, true); + assert.equal(limiter.consume('other').allowed, true); + assert.equal(limiter.consume('').allowed, false); + limiter.reset(''); + assert.equal(limiter.consume('').allowed, true); + assert.equal(limiter.consume('other').allowed, false); + } finally { limiter.destroy(); } + }); + test(`${Type.name} omitted reset key still resets every key`, () => { + const limiter = new Type({ maxRequests: 1, windowMs: 60_000 }); + try { + limiter.consume(''); limiter.consume('other'); + limiter.reset(); + assert.equal(limiter.consume('').allowed, true); + assert.equal(limiter.consume('other').allowed, true); + } finally { limiter.destroy(); } + }); +} diff --git a/v3/@claude-flow/shared/src/resilience/rate-limiter.ts b/v3/@claude-flow/shared/src/resilience/rate-limiter.ts index 2da7aa4295..ea54728cc5 100644 --- a/v3/@claude-flow/shared/src/resilience/rate-limiter.ts +++ b/v3/@claude-flow/shared/src/resilience/rate-limiter.ts @@ -161,7 +161,7 @@ export class SlidingWindowRateLimiter implements RateLimiter { * Reset rate limit for a key */ reset(key?: string): void { - if (key) { + if (key !== undefined) { this.requests.delete(key); } else { this.requests.clear(); @@ -317,7 +317,7 @@ export class TokenBucketRateLimiter implements RateLimiter { * Reset bucket for a key */ reset(key?: string): void { - if (key) { + if (key !== undefined) { this.buckets.delete(key); } else { this.buckets.clear();