Repository navigation
Description
Problem
ModelUsage.dayReports (packages/core/src/model-usage.ts:246-255) accumulates each day's per-provider share in a plain object literal keyed by provider ID:
const providers: Record<string, { requests: number; metered: number; costUsd: number }> = {}
for (const row of dayRows) {
const current = providers[row.providerID] ?? { requests: 0, metered: 0, costUsd: 0 }
providers[row.providerID] = { requests: current.requests + row.requests, ... }
}Provider IDs are user-chosen keys (custom providers are defined by name in config) and reach this function from the recorded route history. Any ID that collides with an Object.prototype member corrupts the report:
__proto__— the read returns the prototype (not the zero record), and the assignment sets the object's prototype instead of an own property: the provider vanishes from the report entirely.constructor,toString,valueOf,hasOwnProperty, … — the read returns the inherited member (a function, notundefined), socurrent.requestsisundefinedand the provider's share becomes NaN (serialised asnull).
The day totals stay correct (they are summed from the row array, not the record), so the corruption is silent: the per-provider breakdown — the part of the route_usage day report that answers "which provider did this day use" — drops or NaNs one provider's share with no error anywhere.
This is the same collision class the codebase already fixed for config alias resolution in #538, which moved user-keyed copies to Object.fromEntries; this accumulator was not part of that audit.
Evidence (verified this run, main 45e00c8f48)
Laptop bun probe importing the real ModelUsage module (probe at laptop ~/git/fleet/probe-2026-10-27-2335/probe.ts; model-usage.ts md5 cf82532048c7fea28a8257223eb47878, byte-identical on both checkouts):
- Input rows for one day:
anthropic(3 requests),__proto__(7 requests),constructor(5 requests), each 15 metered tokens / $1. - Output
providersJSON:{"anthropic":{"requests":3,"metered":15,"costUsd":1},"constructor":{"requests":null,"metered":null,"costUsd":null}}—__proto__absent (own keys are exactly["anthropic", "constructor"]),constructor's numbers are NaN. - Output day total:
requests: 15— correct, which is why nothing downstream notices.
Existing tests (packages/core/test/model-usage.test.ts, "days group by date and keep each provider's share") cover only openai/anthropic IDs.
Acceptance criteria
dayReportsreports a correct own entry for provider IDs__proto__,constructor, andtoString(requests/metered/cost summed across that provider's rows for the day, including multiple rows with the same colliding ID).- Ordinary provider IDs produce exactly the same reports as before, and the returned
providersvalue serialises withJSON.stringifyincluding the colliding IDs. - The fix uses a prototype-safe construction (e.g. accumulate in a
Mapand/or build the record withObject.fromEntries, as fix(core): config alias resolution drops an entry named __proto__ #538 did) rather than blocklisting names. - Regression tests in
packages/core/test/model-usage.test.tsbeside the existing day-grouping test; CI green.
Activity
developing-today commentedon Oct 28, 2026
OPENCODE-127
dezren39 commentedon Oct 28, 2026
🤖 Agent note · filed
3 remaining items
dezren39 commentedon Oct 28, 2026
🤖 Agent note · merged
useRouteDatato PermissionPrompt on main (2026-10-24); this PR touches only packages/core. Re-verified the core suite on the laptop at PR head f590469:bun test test/model-usage.test.ts10/10 pass, including the new Object.prototype-collision regression test. e2e jobs were still queued in the CI backlog. Merged under the standing merge-if-good directive; this issue auto-closed as completed.