Skip to content

[Story 1.1] Extract RateLimitClock singleton #361

Description

@edelauna

Part of #356 (Epic 1: Foundation).

Depends on: Nothing — additive, no behavior change.

Context

Task.lastGlobalApiRequestTime is private static (line 287, Task.ts) — shared across all Task instances in the Node.js module. When two subtasks run concurrently they stomp on each other's rate-limit clock, causing both to see stale timestamps and either over-delay or skip the delay entirely. This must be fixed before any concurrent task execution is safe.

Developer Notes

Create src/core/task/RateLimitClock.ts:

  • RateLimitClock class with getLastRequestTime(): number | undefined and recordRequest(): void.
  • Export createRateLimitClock(): RateLimitClock.

In src/core/task/Task.ts:

  • Remove private static lastGlobalApiRequestTime?: number (line 287) and static resetGlobalApiRequestTime() (line 294).
  • Add private rateLimitClock: RateLimitClock as a constructor-injected field with a default factory call so existing callsites need no change.
  • Replace 6 call sites with this.rateLimitClock.* (lines 2440, 3849, 3854, 3902, 4277–4278).
  • Add optional rateLimitClock?: RateLimitClock to TaskOptions.

In src/core/webview/ClineProvider.ts:

  • Construct one RateLimitClock per provider; pass it to createTask() so all tasks under one provider share the clock, preserving the original rate-limit intent across subtasks.
  • Behavioral note: The current private static field is global across ALL providers in the Node.js module. Per-provider scoping means two providers with different API keys no longer share rate-limit state. This is intentionally better, but verify existing rate-limit tests don't assume cross-provider sharing.
  • Multi-window analysis: Each VS Code window runs its own extension host (separate Node.js process), so per-provider scoping does not introduce a regression for multi-window setups.

Files: src/core/task/RateLimitClock.ts (new), src/core/task/Task.ts, src/core/webview/ClineProvider.ts

Tests (src/core/task/__tests__/RateLimitClock.spec.ts):

  • Two Task instances with the same clock share rate-limit waits (second waits for first to clear).
  • Two Task instances with different clocks are fully independent (validates per-provider scoping).
  • First call (no prior time) returns no delay.
  • Time elapsed < limit → delay applied; time elapsed > limit → no delay.

Acceptance Criteria

  • Task.resetGlobalApiRequestTime() is deleted; grep -n "Task.lastGlobalApiRequestTime" src/core/task/Task.ts returns zero matches.
  • All existing Task.spec.ts rate-limiting tests pass without modification.
  • No static state remains on Task for rate limiting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions