Skip to content

fix: estimateCost prices an unknown model as free — a measurement failure reported as a measured zero #424

Open

Description

@bradtaylorsf

Summary

estimateCost() returns a confident 0 when the model key is unknown. It cannot distinguish "this stage was free" from "I have no idea what this stage cost."

// src/lib/config.ts:370-379
export function estimateCost(
  model: string,
  inputTokens: number,
  outputTokens: number,
  pricing: Record<string, ModelPricing>,
): number {
  const p = pricing[model];
  if (!p) return 0;          // <-- an unknown key is priced as free
  return (inputTokens * p.input + outputTokens * p.output) / 1_000_000;
}

A missing pricing entry is a measurement failure, and the function reports it as a measured zero.

Why this is a defect and not a config problem

This repo never sets a model, so the lookup key is the empty string and never resolves:

  • .alpha-loop.yaml:24 — agent: codex ✅ set
  • .alpha-loop.yaml:25 — # model: "" ❌ commented out
  • .alpha-loop.yaml:69-80, 94-102 — every per-step and per-role model: override ❌ commented out

pricing[''] is therefore always undefined, and every stage is priced at $0.00. You can see the empty key in any run log — every stage prints:

Agent: codex | Model:  | CWD: …

(Confirmed in two independent runs on 2026-08-15, hours apart: the epic #415 run and the issue #410 run.)

Setting model: would make this repo's numbers resolve, but it would not fix the function. estimateCost should not return a number that means "free" when it has no basis for one, whatever the config says. Any future agent, model, or provider that isn't in the pricing map gets silently priced at zero, and nothing anywhere says so.

Impact

Total cost: $0.0000 was displayed over 4.4M input / 3.4M output tokens through the implement stage alone. Zero is not a plausible reading of that, but nothing in the output invited doubt.

#394 (shipped in PR #414) fixed the reporting layer: report routing now prints Cost coverage: 0/36 stage invocation(s) and Total cost: n/a instead of a fake $0.00. That is a real improvement and it is what surfaced this. But the source function still returns 0, so:

  • every other caller of estimateCost still gets a confident zero — src/lib/pipeline.ts:157 writes it into telemetry as cost_usd, where it is indistinguishable from a genuine zero;
  • the n/a in the report is reconstructed downstream from coverage counting, not from the pricing function admitting it failed.

This is the same failure shape as #400 (closeIssue logging [OK] Closed for closes that never happened), #404 (merging before checks report), and #412 (mutation wrappers returning void on failure): a function reporting success for something it did not do. Cost is the instrument used to steer routing decisions, so a false zero propagates into how work gets scheduled.

Fix

  1. Return "unknown", not zero. Change the signature to number | null (or { usd: number; measured: boolean }) and return null/measured: false on a pricing miss. Zero must mean zero.
  2. Update the callers. src/lib/pipeline.ts:157 should persist cost_usd: null rather than 0 so telemetry records the gap at the point of measurement instead of inferring it later.
  3. Warn once per unknown key per run — Model "" has no pricing entry; stage costs will be unmeasured — so an unpriced configuration is visible when it happens, not at report time.
  4. Uncomment / set model: in .alpha-loop.yaml so the key resolves at all. Note this is a separate, secondary action: it makes numbers appear, but they would be estimates from configured rates, not measured actuals. Real numbers need model attribution on the Codex path — see the scope note below.

Verify

  • estimateCost('', 1000, 1000, {}) returns null/unmeasured, not 0.
  • estimateCost('some-model', …) with a valid entry is unchanged.
  • A run whose model has no pricing entry emits exactly one warning and writes cost_usd: null to telemetry.
  • report routing still prints n/a and its coverage line — now sourced from the pricing function's own admission rather than reconstructed.
  • Regression: a genuinely free stage (0 tokens, valid pricing) still reports $0.00 and counts as measured.

Scope note — what this does and does not buy

Fixing this makes the loop honest. It does not make cost measured. Whether Codex stages should be priced at all — and from what model attribution, since step overrides set agent: but never model:, leaving no pricing key to look up — is a separate design decision that #394 explicitly left open. This issue is only about a function that answers "I don't know" with "zero".


Found 2026-08-15 while gating PR #414 for #394. Filed by the coder agent on its own initiative; close it if you'd rather treat the whole area as a scope decision.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions