Skip to content

iterate-loop's totalTurnsUsed/totalCostUsd accumulate unclamped, unlike the sibling tracker.totals two lines below #7246

Description

@JSONbored

Context

packages/loopover-engine/src/miner/iterate-loop.ts defines finiteNonNegativeUsage (line 285-293) specifically to guard against malformed driver-reported usage numbers:

A finite, non-negative usage value, else 0. accumulateAttemptUsage (attempt-metering.ts) deliberately THROWS a RangeError on a negative/non-finite input to protect its own direct callers... clamp here too so no current or future driver can crash the loop instead of being governed.

Inside runIterateLoopCore's per-iteration loop (around line 420-434), this guard is applied inconsistently on two adjacent statements reading the exact same driverResult fields:

totalTurnsUsed += driverResult.turnsUsed ?? 0;
totalCostUsd += driverResult.costUsd ?? 0;
// ...
tracker.totals = accumulateAttemptUsage(tracker.totals, {
  tokens: finiteNonNegativeUsage(driverResult.tokensUsed),
  turns: finiteNonNegativeUsage(driverResult.turnsUsed),
  // ...
  costUsd: finiteNonNegativeUsage(driverResult.costUsd),
});

tracker.totals (used for budget-ceiling enforcement) correctly clamps turnsUsed/costUsd through finiteNonNegativeUsage before folding them in via accumulateAttemptUsage. The totalTurnsUsed/totalCostUsd accumulators two lines above do not — they only apply ?? 0 (which catches undefined/null but not NaN or a negative number) before a raw +=.

driverResult.turnsUsed/costUsd come from CodingAgentDriverResult, a value a driver implementation reports. The module's own reasoning for the clamp — "no current or future driver can crash the loop" — applies equally to a driver reporting NaN or a negative value here: NaN + x poisons the accumulator for the rest of the loop (NaN is sticky under +=), and a negative value silently understates the running total. Both totalTurnsUsed and totalCostUsd are returned to the caller as IterateLoopResult.totalTurnsUsed/totalCostUsd — a wrong (possibly NaN, possibly under-counted) total silently reaches any log, dashboard, or billing/telemetry consumer that reads the loop result, even though the loop's own budget enforcement (via tracker.totals) stays correct because it's guarded.

Requirements

  • Apply finiteNonNegativeUsage(driverResult.turnsUsed) and finiteNonNegativeUsage(driverResult.costUsd) to the totalTurnsUsed/totalCostUsd accumulator updates, the same guard already used two lines below for tracker.totals.
  • Do not change finiteNonNegativeUsage's own signature or behavior — this is a call-site fix, applying an existing guard consistently, not introducing new clamping logic.

Deliverables

  • totalTurnsUsed += finiteNonNegativeUsage(driverResult.turnsUsed); (replacing the unguarded ?? 0 form)
  • totalCostUsd += finiteNonNegativeUsage(driverResult.costUsd); (replacing the unguarded ?? 0 form)
  • Regression test: a fake driver result reporting turnsUsed: NaN (or a negative costUsd) across two iterations does NOT poison IterateLoopResult.totalTurnsUsed/totalCostUsd with NaN or a negative running total — the malformed value is treated as 0 for the accumulator, matching how tracker.totals already handles it

Test Coverage Requirements

This repo's Codecov patch gate is 99%+ hard (branch-counted) on every changed line/branch in src/**/packages/**. The regression test must reproduce the exact NaN-poisoning failure mode described (assert the final totalTurnsUsed/totalCostUsd on IterateLoopResult is a finite, correct number after a malformed-usage iteration, not NaN).

Expected Outcome

A driver reporting a malformed (NaN/negative) turnsUsed or costUsd can no longer silently poison the loop's returned total usage figures — totalTurnsUsed/totalCostUsd are governed by the same guard as the budget-enforcement totals, consistent with the module's own stated defensive intent.

Links & Resources

packages/loopover-engine/src/miner/iterate-loop.ts:285-293 (finiteNonNegativeUsage and its stated purpose), :423-424 (the unguarded accumulator updates), :429-432 (the guarded sibling two lines below, showing the correct pattern).

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions