Repository navigation
fix: estimateCost prices an unknown model as free — a measurement failure reported as a measured zero #424
Description
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-rolemodel: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
estimateCoststill gets a confident zero —src/lib/pipeline.ts:157writes it into telemetry ascost_usd, where it is indistinguishable from a genuine zero; - the
n/ain 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
- Return "unknown", not zero. Change the signature to
number | null(or{ usd: number; measured: boolean }) and returnnull/measured: falseon a pricing miss. Zero must mean zero. - Update the callers.
src/lib/pipeline.ts:157should persistcost_usd: nullrather than0so telemetry records the gap at the point of measurement instead of inferring it later. - 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. - Uncomment / set
model:in.alpha-loop.yamlso 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, {})returnsnull/unmeasured, not0.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: nullto telemetry. report routingstill printsn/aand 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.00and 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