Skip to content

fix(pricing): gpt-5.6 terra/luna carried the launch tiers; luna was 5x over - #29

Merged
pa-arth merged 1 commit into
mainfrom
fix/gpt-56-terra-luna-rates
Aug 14, 2026
Merged

fix(pricing): gpt-5.6 terra/luna carried the launch tiers; luna was 5x over#29
pa-arth merged 1 commit into
mainfrom
fix/gpt-56-terra-luna-rates

Conversation

@pa-arth

@pa-arth pa-arth commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Turns main green. pricingDrift has been the only failure since 2026-08-13 and the suite is now 405/405 for the first time.

Who was right

cc-audit (before) backend config-cost LiteLLM verdict
gpt-5.6-terra 2.5 / 0.25 / 15 2 / 0.2 / 12 2 / 0.2 / 12 cc-audit stale
gpt-5.6-luna 1 / 0.1 / 6 0.2 / 0.02 / 1.2 0.2 / 0.02 / 1.2 cc-audit stale

Not a new discovery. promptster-backend settled it on 2026-07-31 in f4500a27, with Paarth confirming LiteLLM against the vendor: "luna was repriced after GA and our rows still carried the launch tier." This copy never got the correction.

I checked the agreement wasn't circular before trusting it. 39e47f6e (backend #319) introduced 2.5/0.25/15 and 1/0.1/6; cc-audit #19 ported those verbatim and was correct at the time; f4500a27 corrected the backend against the vendor; cc-audit didn't move. Both tables also already agree with LiteLLM on gpt-5.6 and gpt-5.6-sol (5/0.5/30), so the drift is isolated to the two repriced rows. The 10%-of-input cached-rate invariant in the header still holds at the new numbers.

The actual defect is the mirror, not the two numbers

src/vendor/pricing.ts is hand-copied from packages/config-cost and has no subscriber to that repo — cc-audit deliberately does not depend on it. A mirror like that can only go stale silently, and did, for two weeks.

So I diffed the two tables in full rather than patching the two rows I already knew about: 29 OpenAI rows and 16 Anthropic rows on both sides, identical key sets, and these two rows are the only divergence. One missed sync, not a pattern. Recorded in the header so the next reader knows the mirror is a mirror.

pricingDrift.test.ts is the one thing that notices, and it did its job — it went red the first time CI ran after the repricing and stayed red rather than being relaxed to fit. Its instruction, inherited from the commit that fixed the backend side: find out who is right, not update the expectation. That is now written at the top of the table.

Shipped impact — needs a release, not just a merge

0.7.0 is the published version and went out 2026-07-29, two days before the correction. Any user auditing Codex sessions on gpt-5.6-luna has been seeing a 5x overstatement of those turns; terra 1.25x.

A wrong-but-present rate is quieter than this table's famous failures — a missing model bills $0 and vanishes from every spend view, a too-eager prefix match bills 12x under — because every downstream number still looks like a number. Nothing is zero, nothing is absent, the totals are just wrong.

Uploaded aggregates already in cost_audit_aggregates / solo_captures keep the overstated figures. This fixes the rate, not the stored rows.

Verification

  • npx tsc --noEmit clean.
  • npx vitest run405 passed (405), 40 files, 0 failures.

🤖 Generated with Claude Code

…x over

  terra  2.5 / 15 / 0.25  ->  2 / 12 / 0.2
  luna   1   /  6 / 0.1   ->  0.2 / 1.2 / 0.02

Not a new discovery — promptster-backend settled this on 2026-07-31 in f4500a27,
Paarth confirming LiteLLM against the vendor: both models were repriced after GA
and the rows still carried launch day. This copy never got the correction.

WHICH IS THE ACTUAL DEFECT. src/vendor/pricing.ts is a hand-copied mirror of
promptster-backend packages/config-cost, and it has no subscriber to that repo —
cc-audit deliberately does not depend on it, so the mirror can only go stale
silently, and did, for two weeks. Diffed the two tables in full while here: 29
OpenAI rows and 16 Anthropic rows on both sides, identical key sets, and these
two rows were the ONLY divergence. One missed sync, not a pattern.

pricingDrift.test.ts is the one thing that notices, and it worked — it went red
the first time CI ran after the repricing (main has been red on it since
2026-08-13) and stayed red rather than being relaxed. The suite is now 405/405
for the first time. A red run there is an instruction to find out who is right,
not to update the expectation; the header comment now says so.

SHIPPED IMPACT: 0.7.0 is the published version and went out 2026-07-29, two days
before the correction. Any user auditing Codex sessions on gpt-5.6-luna has been
seeing a 5x overstatement of those turns, terra 1.25x. A wrong-but-present rate
is quieter than the failures this table is famous for — a missing model bills $0
and vanishes, a bad prefix match bills 12x under — because every downstream
number still looks like a number. Needs a release, not just a merge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR corrects the mirrored OpenAI pricing for GPT-5.6 Terra and Luna after their post-GA repricing.

  • Changes Terra rates from 2.5/0.25/15 to 2/0.2/12.
  • Changes Luna rates from 1/0.1/6 to 0.2/0.02/1.2.
  • Documents the table’s manual synchronization process and repricing history.

Confidence Score: 4/5

The PR appears safe to merge, with the non-blocking concern that the corrected rates are not protected by an offline regression test.

The changed rates flow correctly into existing OpenAI cost calculations and match the stated correction, but network-degraded test runs cannot detect a future regression of these exact values.

Files Needing Attention: src/vendor/pricing.ts

Important Files Changed

Filename Overview
src/vendor/pricing.ts Corrects the two stale pricing rows and improves mirror documentation, but leaves the corrected values without deterministic offline regression coverage.

Comments Outside Diff (1)

  1. src/vendor/pricing.ts, line 391-392 (link)

    P2 Corrected rates lack offline coverage

    The corrected gpt-5.6-terra and gpt-5.6-luna values are only covered by the network-dependent drift check, so an offline or network-degraded test run can pass after these rates regress and allow the same silent spend overstatement to return. Please add deterministic assertions for both corrected rows.

    Fix in Claude Code Fix in Cursor

Fix all with Greploop

Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "fix(pricing): gpt-5.6 terra/luna carried..." | Re-trigger Greptile

@pa-arth
pa-arth merged commit 3e53c77 into main Aug 14, 2026
3 checks passed
@pa-arth
pa-arth deleted the fix/gpt-56-terra-luna-rates branch August 14, 2026 16:21
pa-arth added a commit that referenced this pull request Aug 15, 2026
…later turn (#30)

Review findings from #28 and #29, both real.

### 1. Resumed prefix used the wrong turn (#28, Greptile)

`injectedOpen` closes at the first main-chain assistant row — 34 lines BEFORE the
cross-file `seen` dedup — while `computeAlwaysOn` measures the prefix from
`firstMain`, the first RETAINED turn. On a resumed transcript those are different
rows: the closing row is a replay owned by an earlier file and gets dropped, so
`firstMain` lands on a later turn whose cacheRead has the whole replayed
conversation folded in. Turn-1-sized `attributed` against a much larger `measured`
INFLATES `fixedPrefixTokens`, and `reconcile()` cannot catch it because it only
fires on a NEGATIVE remainder. Inflation is the silent direction.

Closing before the dedup was deliberate and stays — it is what stops a whole
resumed session's attachments folding into "turn 1". The bug was the other half.

Fixed by recording which row closed the window and which row `firstMain` will
pick, and declining to measure the session when they differ — counted, not
silently dropped. `standingContextTokens` is untouched: a resumed turn really did
carry that much. Only the BREAKDOWN is unanswerable, and `unmeasured` now says so
with its own reason rather than borrowing the no-attachments one.

**ZERO INSTANCES IN THE AUTHORING CORPUS: 0 of 483 sessions with attachments, of
926.** Verified by same-instant runs of origin/main and this branch — 0 of 30
fields moved. So this fixes no number today; it closes a path that would have been
invisible when it did fire.

That also means the case cannot be recorded, and a suite running only real local
data would stay green with the guard deleted. It is built from RECORDED bytes
anyway: a resumed replay is "rows this parse has seen, then rows it has not", so
seeding `seen` from a prefix of a recorded transcript and re-parsing the whole of
it reproduces the shape exactly, with no invented rows.

Two recorder bugs surfaced doing that, both the fixture-encodes-an-impossible-shape
failure the recorder exists to prevent:
- `message.id` was filled with CONSTANT filler, so every assistant row shared an id
  and the parser merged them — a "two-turn" recording came back as one turn with one
  prefix. Identity keys are now a memoized bijection: distinctness and intentional
  sharing both survive, no real value is carried. The old comment claimed ids "key
  nothing across fixtures"; they key the dedup and the streamed-row merge.
- the recorder counted assistant ROWS, not turns. Now counts distinct message ids.

### 2. Corrected rates had no offline coverage (#29, Greptile)

`pricingDrift` degrades to pass when it cannot fetch — correct, and it means an
offline run proves nothing about the rows it just corrected. `pricingPinned.test.ts`
is the deterministic companion: drift asks whether the VENDOR moved, this asks
whether OUR table moved underneath us. Only rows with a reason to be pinned are in
it; pinning all 29 would train people to update the expectation to match the code.

### 3. A stale comment that would have caused the next mis-pricing

Writing that test failed it, on `gpt-5.5-pro`. The header claimed "the `pro` tiers
publish no cached-input rate" and are set to cachedInput == input. **False since
gpt-5.4-pro/5.5-pro**: both publish 3/M, the ordinary 10%, confirmed against
LiteLLM, and the table correctly carries it. A reader trusting that comment would
have "restored" them to equal-input and over-billed every cached token they read.
The real exception set is three rows and is now named with reasons rather than
derived from the word "pro" — gpt-5-pro and gpt-5.2-pro (genuinely no published
rate) and codex-mini-latest (genuinely 25%).

410/410 tests, typecheck and lint clean. Mutation-checked: reverting luna to the
launch tier, "fixing" gpt-5.5-pro to equal-input, and dropping the prefix-turn gate
each fail only their own test. The two-turn fixture asserts its own precondition
(turn 2's prefix 61,014 -> 61,863) so a future re-recording cannot quietly leave the
guard proving nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Greptile P2 on #30, valid — and the failure was one step worse than reported.
`tool_result.tool_use_id` names a `tool_use.id`, which `claudeCode.ts:270` reads to
fold is_error and the result timestamp back onto the issuing turn. `id` went
through the identity bijection; `tool_use_id` fell through to length-preserving
filler. Because it is LENGTH-preserving, the two real ids were the same length and
both results came out as the same string — so the fixture did not merely lose the
link, it pointed two distinct calls at one key. A tool_result naming a tool_use
that does not exist is a transcript Claude Code cannot emit, which is the exact
class of defect recording fixtures exists to prevent (and the second instance of it
in this PR).

Added to IDENTITY and the two-turn fixture re-recorded from the same transcript: 4
lines change, the recorded prefixes are byte-identical (61,014 -> 61,863), so the
resumed-transcript assertions rest on the same numbers as before.

Also asserted, across EVERY injected-prefix fixture rather than the one that
regressed: every tool_use_id resolves to a tool_use in the same file, and distinct
results name distinct calls. The second half is not redundant — containment alone
passes the moment filler collides with a real id, which is how this hid.

414/414 (the fixture join check runs once per fixture file), typecheck and lint clean. Checked the guard bites: the pre-fix fixture
fails it by name ("dangling tool_use_id redacted-fixture-content redac").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pa-arth pa-arth mentioned this pull request Aug 15, 2026
pa-arth added a commit that referenced this pull request Aug 15, 2026
Four merged PRs since 0.7.0 (#27 #28 #29 #30), and two of them change numbers a
user has already read, which is what makes this a release rather than a merge.

MINOR, not patch: #28 changes what the always-on breakdown MEANS — measured off the
transcript instead of censused off disk — and takes the aggregate schemaVersion 9 ->
10, removing `skillDescriptionTokens` rather than keeping a field whose name outlived
its meaning.

THE ONE TO SHIP FOR: 0.7.0 prices gpt-5.6-luna 5x over and terra 1.25x over on every
Codex session it has audited since the vendor repriced (backend f4500a27, 2026-07-31,
two days after 0.7.0 went out). That was fixed on main on 2026-08-14 and reaches
nobody until this publishes — a merged pricing fix in an unreleased CLI is a fix that
does not exist for users.

Verified before tagging, not just built:
- 414/414 tests, typecheck and lint clean
- `npm run build:npm` -> 411.9kb single file, `--version` reports 0.8.0 (so the
  esbuild --define baked the bump, which is the thing a version bump can silently
  miss)
- ran the BUNDLE against the real local corpus, not just `--help`: schemaVersion 10,
  standingContextTokens 60,963 — within 0.4% of the 60,738 measured independently by
  differencing `claude -p` runs — `skillDescriptionTokens` absent from the payload as
  #28 intended, `unmeasured` empty.

Tag v0.8.0 after merge; .github/workflows/publish.yml guards tag == package.json and
publishes + attaches the standalone binaries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant