Skip to content

aggregator(quota-gather-map): entryFromResult throws RangeError on an out-of-range fetchedAt — one bad provider stamp discards the provider's whole tick #2332

Closed

Description

@dezren39

Summary

entryFromResult in the quota gatherer (pm-stack/aggregator/src/quota-gather-map.ts:173-177) converts a provider result's fetchedAt epoch-ms value with new Date(result.fetchedAt).toISOString() after checking only typeof === "number" and Number.isFinite. A finite value outside the JS Date range (max ±8.64e15 ms) makes new Date(...) an Invalid Date, and .toISOString() on it throws RangeError: Invalid Date. Because the gatherer maps every result for a provider in one .map() call, that one bad result discards the provider's whole tick.

The same file already knows this guard: normalizeResetAt (quota-gather-map.ts:44-49) checks Number.isNaN(d.getTime()) ? null : d.toISOString() for window resetAt values — fetchedAt is the one conversion in the file left unguarded.

Evidence (verified this run on main 5a34fa70)

  • pm-stack/aggregator/src/quota-gather-map.ts:173-177 — measuredAt is typeof result.fetchedAt === "number" && Number.isFinite(result.fetchedAt) ? new Date(result.fetchedAt).toISOString() : tickIso. The tickIso fallback exists for missing/non-finite values, but a finite out-of-range value takes the throwing branch instead of the fallback.
  • pm-stack/aggregator/scripts/gather-quota.ts:199 — const entries = results.map((r) => entryFromResult(r, tickIso)) runs inside runProvider's try/catch (~:196-215): a throw returns { state: "error", entries: [] } for that provider, so every credential/window that provider returned correctly in the same tick is dropped with it.
  • Probe (Bun, real quota-gather-map.ts imported from main, /tmp/probe-laneB-fetchat/probe.ts):
    • control fetchedAt: 1793075143879 → OK, measuredAt=2026-10-27T04:25:43.879Z
    • fetchedAt: 1e16 → THREW RangeError Invalid Date
    • fetchedAt: 9e15 (just over the Date maximum) → THREW RangeError
    • fetchedAt: -1e16 → THREW RangeError
    • batch map of [good result, fetchedAt: 1e16 result] → THREW — whole map lost
    • in-file control: normalizeResetAt(1e16) → null (guarded, no throw)
  • Secondary, same gate: fetchedAt: 0 yields measuredAt=1970-01-01… instead of the tick fallback, so a zero stamp renders a fresh read as ~56 years old / instantly stale. (normalizeResetAt requires v > 0; entryFromResult has no such check.)

Why it matters

fetchedAt is produced by the OpenChamber quota module, outside this repo — the mapper is exactly the boundary that is supposed to absorb a provider's bad stamp (its header, quota-gather-map.ts:1-15, promises a provider whose usage is not well-shaped "yields no windows (never zeros)", i.e. degrade per-entry, never fail the tick). Today one out-of-range stamp — e.g. a provider computing in microseconds or nanoseconds (1e16 is a plausible µs-scale value for 2026), or a corrupted upstream timestamp — turns a successful fetch into a provider-wide error entry every 15-minute tick until the provider is fixed, with the gatherer's error text (RangeError: Invalid Date) naming neither the provider field nor the value.

This is the same defect class as #2286 (iso() in the OpenCode session mapper), in a different module with a different data source and blast radius: there one bad session dropped a server's session feed; here one bad stamp drops a provider's quota tick.

Acceptance criteria

  • entryFromResult never throws for any numeric fetchedAt: a finite value that does not produce a valid Date (out of range, or <= 0) falls back to tickIso, exactly as a missing/non-finite value already does.
  • One result with a bad fetchedAt no longer discards the other results in the same provider batch — the bad entry is still produced (with the tick stamp), not dropped.
  • Regression tests in pm-stack/aggregator/test/quota-gather-map.test.ts pin: 1e16, -1e16, and 0 → measuredAt === tickIso with windows intact; the existing ms control (1793075143879 → 2026-10-27T04:25:43.879Z) is unchanged.
  • No change to normalizeResetAt, the file-export path (entryFromFileExport, whose measuredAt parse is already NaN-gated), or the quota-current contract shape.

Dedup

Searched open + closed issues for quota-gather-map, entryFromResult, fetchedAt, and RangeError Invalid Date aggregator: #2286 is the OpenCode session mapper (sources/opencode.ts), distinct file/source; #1624 is gatherer snapshot dedupe by credentialId, distinct; no filing covers the entryFromResult fetchedAt conversion. The 2026-10-29T10:58Z backlog check that cleared quota-gather-map covered entryFromFileExport's NaN-gated measuredAt only.

Activity

  1. developing-today commented on Oct 29, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    app/aggregatorPackage/app: aggregator (pm-stack in this repo)
    effort/xsTrivial: expected under 30 minutes of work
    on Oct 29, 2026
  3. dezren39 commented on Oct 29, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · state: filed

    • Agent: new-issue-creator (lane B, new-issue-creator-code-b)
    • Session / ID: new-issue-creator-code-b run 2026-10-29T13:28Z
    • Found via: filed this run
    • Task: Filed aggregator(quota-gather-map): entryFromResult throws RangeError on an out-of-range fetchedAt — one bad provider stamp discards the provider's whole tick #2332 — entryFromResult (quota-gather-map.ts:173-177) throws RangeError on a finite out-of-range fetchedAt; the gatherer's per-provider .map() loses the whole tick. Verified with a Bun probe against the real module on main 5a34fa7 (control OK; 1e16 / 9e15 / -1e16 threw; normalizeResetAt in the same file is guarded and returns null).
    • Expectation: effort/xs triage — pin the fallback rule (invalid/<=0 fetchedAt -> tickIso, the existing missing/non-finite fallback), then route a small_fix packet with regression tests.
    • Links: packet queue-pilot/packet-2332-triage.json (enqueued this run); evidence issue-code-quotagather-fetchedat-body.md in the goal hidden_files
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  5. 6 remaining items

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  7. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  8. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    task/small-fixQueue packet kind: bounded fix with reproduction and targeted tests
    and removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 2, 2026
  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/aggregatorPackage/app: aggregator (pm-stack in this repo)app/pm-stackpm-stack (Pad/Grist) in developing-today/codeeffort/xsTrivial: expected under 30 minutes of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions