Repository navigation
Conversation
- tools/rates.json: add the wire id, ordered before claude-opus-5 so a future dated 5.5 snapshot resolves via cost-report's prefix match rather than falling through to Opus 5's rate. - tools/update-rates.mjs: map the display name, require the wire id on every refresh, accept the pricing page's footnote marker on the cache-read cell, and validate that cell against its documented 0.05x discount instead of the standard 0.1x (a per-model override, not a loosened guard for every other row). - proxy/model-families.mjs: classify bare claude-opus-5 and claude-sonnet-5 ids (and by prefix, claude-opus-5-5) so served-model divergence detection stops reporting them unknown. - Tests: the fixture gains the live Opus 5.5 row: cost-report priced a 1M/1M/1M call at $30.5 (Opus 5's rate) before this change, $24.2 after. Co-Authored-By: Claude <noreply@anthropic.com>
- CHANGELOG.md: replace the false "fell back to its longest-matching neighbor" with what lookupRates() actually does (first rates.json key in file order where either id prefixes the other); fold in the model_divergence_sticky latch-timing fix (first divergent turn, not third) as the same bullet. - tools/update-rates.mjs PRICE_CELL: narrow the footnote tolerance to exactly one space + 1-2 digits, matching what tableRows() actually leaves behind, so junk like "$4 / MTok2026" fails closed. - test/tools-update-rates.test.mjs: red-first case for the narrowed regex, and note the Opus 5.5 fixture row was copied in from the later live page. Co-Authored-By: Claude <noreply@anthropic.com>
codeslake
marked this pull request as ready for review
September 23, 2026 06:24
Claude Code 2.1.284's default Sonnet is claude-sonnet-5-5. update-rates never wrote it (no NAME_TO_WIRE / REQUIRED_WIRE_IDS entry), rates.json had no row so the session-budget-breaker's exact-id lookup counted no cost for it, and the tool-addition probe still named claude-sonnet-5. Co-Authored-By: Claude <noreply@anthropic.com>
…osition The live pricing page now has two header rows and orders the columns Input | Output | 5m writes | 1h writes | Hits and refreshes. priceRow() read them by fixed position, so every real refresh parsed 0 rates and 27 errors and failed closed. Take the column order from the header row that names Input/Output/5m/1h/Hits, per table; a header that does not name each once yields no rows (fails closed), never a guessed order. Fixture: the model-pricing table of the 2026-09-28 live page. Inline test tables gain a header row, since a table without one now yields no rows. Co-Authored-By: Claude <noreply@anthropic.com>
…uest stashed on meta onStreamEvent read the session id from ctx.headers, but the real stream ctx (proxy/stream.mjs) has no request headers, so no streamed request accrued and the token, cost and rate levers never fired on real traffic. Read ctx.meta._sbbSessionId, as onResponse does. Stream fixtures now model the real ctx, and one test drives the shipped streamResponse. The sim's stream legs carried the same wrong shape and are corrected. Co-Authored-By: Claude <noreply@anthropic.com>
…5.5) modelFamily() read claude-haiku-5-5 as unknown (the claude-haiku-4-5 root cannot match), and rates.json had no entry, so the session-budget-breaker cost lever counted $0 for the CC 2.1.293 default Haiku. Add the family root and the lower-tier (up to 100,000 tokens) rates. The live page tiers this row, so the parser reads no row for it and a refresh keeps the hand-written entry (measured against the live page); NAME_TO_WIRE prices it should the page go flat. Co-Authored-By: Claude <noreply@anthropic.com>
The shipped-entry comparison read undefined == undefined on a tree with no claude-haiku-5-5 entry and passed. Compare both the flat-row parse and rates.json to the literal rates; carry the ponytail marker for the lower-tier ceiling on the NAME_TO_WIRE row. Co-Authored-By: Claude <noreply@anthropic.com>
The 'direct is the LAST resort' case counted two refusal traces for two dead hops but took both ports from freePort(), which gives the number back before returning. The kernel can return the same number to the next call (the relay folds a repeated hop into one) or to the relay's carrier (the relay drops its own address as a hop), and a neighbouring process's listener on a freed port answers as a live hop. Each leaves one trace: '1 refusals traced, want one per hop ... 1 !== 2', measured at ~1% of runs with 40 concurrent runners and reproduced deterministically by forcing either collision. Use ports 1 and 2, as proxy-forward-attach-fallback.test.mjs already does for its dead hop: an unprivileged runner cannot bind below 1024 and a connect there refuses at once. The two other cases in the file that dialled a freePort() hop (the second-hop fall-through, whose trace assertion fails the same way, and REQUIRE_HOP, which passed while silently measuring a no-hop chain) take the same constants. Co-Authored-By: Claude <noreply@anthropic.com>
Claude Code 2.1.296 and the pricing page as of 2026-10-10 both price Sonnet 5.5
cache reads at $0.10 per MTok (was $0.20), 0.05x its $2 input. rates.json still
carried $0.20, so tools/cost-report.mjs over-counted a 1M-read call by $0.10, and
update-rates.mjs refused the live row ("contradicts the documented 0.1x input
multiplier"), leaving rates.json untouched on every refresh.
rates.json takes $0.10. update-rates.mjs adds the 0.05x override Opus 5.5 already
uses, gated to pages dated 2026-10-10 or later: the 2026-09-28 page and the
2026-07-27 fixture still say $0.20 and keep parsing at their own dates.
New fixture pricing-page-2026-10-10.html (the model-pricing table of the live
page). The "rates.json agrees with the page" check now reads it, since it is the
newest page; the older fixtures' own parse assertions are unchanged. cost-report
gains a Sonnet 5.5 case beside the Opus 5.5 one.
Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Claude Code 2.1.280 makes
claude-opus-5-5(Claude Opus 5.5) its default Opus, and none of the model tables knew the id:tools/rates.jsonhad no entry, sosession-budget-breaker's exact rate lookup returned null (fails open), andtools/cost-report.mjs'slookupRates()fell through to the first key in file order that prefixes it,claude-opus-5: 25% high on input and output, 2.5x on cache reads.tools/update-rates.mjshad no name for it, and its multiplier check would have rejected the row anyway: Opus 5.5 prices cache reads at 0.05x input (footnoted on the pricing page), not the standard 0.1x.proxy/model-families.mjshad no root forclaude-opus-5,claude-opus-5-5orclaude-sonnet-5, so all three read"unknown"andcache-telemetry's cross-family divergence latch could not fire for them.Rates, from the 2.1.280 bundle's
tier_4_20_cache_read_0_20and the live pricing page, USD per MTok: input 4, output 20, 5m cache write 5, 1h cache write 8, cache read 0.2.Claude Code 2.1.284 makes
claude-sonnet-5-5(Claude Sonnet 5.5) its default Sonnet, and the same tables lacked it:tools/rates.jsonhad no row (the breaker's exact lookup counted no cost for it, andlookupRates()priced it through theclaude-sonnet-5prefix), andtools/update-rates.mjshad no name for it, so a refresh never wrote it. The live pricing page lists it at input 2, output 10, 5m cache write 2.5, 1h cache write 4, cache read 0.2, the same as Sonnet 5 today.proxy/model-families.mjsalready classifies it assonnetthrough theclaude-sonnet-5root.Two defects surfaced while pricing Sonnet 5.5, both in code this PR already touches:
tools/update-rates.mjsread the pricing table by position (input, 5m, 1h, hits, output). The live page now orders its columns Input, Output, 5m writes, 1h writes, Hits and refreshes under a two-row header, so a real refresh parsed 0 rates with 27 errors and failed closed. With the columns in the right order every parsed rate matchestools/rates.json.session-budget-breaker'sonStreamEventread the session id fromctx.headers, which the stream ctx built inproxy/stream.mjsdoes not carry, so no streamed turn accrued and none of its levers could fire on Claude Code traffic. The breaker's tests passed because their stream fixtures suppliedheaders.Changes
tools/rates.json:claude-opus-5-5, placed beforeclaude-opus-5so the prefix fallback prefers it for a dated 5.5 id;claude-sonnet-5-5, placed beforeclaude-sonnet-5for the same reason.tools/update-rates.mjs:"Claude Sonnet 5.5"inNAME_TO_WIREandREQUIRED_WIRE_IDS.tools/probe-tool-addition.mjs: the default lineup probesclaude-sonnet-5-5in place ofclaude-sonnet-5.tools/update-rates.mjs: each table's column order comes from its own header row, matched by keyword; a header that does not name each price column once yields no rows, so the run still fails closed. New fixturetest/fixtures/pricing-page-2026-09-28.html, the live page's model-pricing table.proxy/extensions/session-budget-breaker.mjs:onStreamEventtakes the session idonRequeststashed onctx.meta, asonResponsealready did. A new test drives the realstreamResponse; the stream fixtures no longer supplyheaders, andtools/sim-session-budget-breaker.mjsbuilds the real ctx shape too. One CHANGELOG Fixed bullet.tools/update-rates.mjs:"Claude Opus 5.5"inNAME_TO_WIREandREQUIRED_WIRE_IDS;PRICE_CELLaccepts one trailing footnote marker ($0.20 / MTok 2, one space and one or two digits, so$4 / MTok2026still fails closed); a per-model cache-read multiplier override (claude-opus-5-5: 0.05) that leaves every other row and field on the standard check.proxy/model-families.mjs: rootsclaude-opus-5->opus,claude-sonnet-5->sonnet.tools/cost-report.mjs: unchanged; the new exact key wins.test/cost-report.test.mjs(new, one case: a 1M/1M/1M-cache-read Opus 5.5 row costs 24.2, it read 30.5 before),test/tools-update-rates.test.mjs(the Opus 5.5 row parses to its own rates; the override does not loosen the guard for other models; a footnote-shaped suffix without the space fails closed; the shippedrates.jsoncarries the id),test/model-families.test.mjs(bare Opus 5, Opus 5.5, Sonnet 5 and Sonnet 5.5 classify),test/proxy-session-budget-breaker.test.mjs(the cost lever pricesclaude-sonnet-5-5instead of failing open), and intest/tools-update-rates.test.mjsthe Sonnet 5.5 row parses as its own row, not as Sonnet 5's. The 2026-07-27 pricing fixture gains the Opus 5.5 and Sonnet 5.5 rows copied from the live page, noted in the test header.CHANGELOG.md: one Fixed bullet.Test plan
node --test: 1957 tests, 1956 pass, 0 fail, 1 skipped.Not changed here
tools/cost-report.mjs --live-rates/--update-ratesstill use its older parser, which needsMTok</td>in every cell, so it drops the footnoted Opus 5.5 row and rewritesrates.jsonwithout it.tools/update-rates.mjsis the validated updater.🤖 Generated with Claude Code