Price 1-hour-TTL cache writes at 2x input (fixes #162) - #163
Open
MildlyMeticulous wants to merge 1 commit into
Open
Price 1-hour-TTL cache writes at 2x input (fixes #162)#163MildlyMeticulous wants to merge 1 commit into
MildlyMeticulous wants to merge 1 commit into
Conversation
Anthropic bills cache writes at 1.25x base input for the 5-minute TTL and
2x for the 1-hour TTL. The scanner read only the aggregate
`cache_creation_input_tokens` and every cost path applied `cache_write`
(the 5-minute rate) to the whole bucket, so 1-hour writes were billed
37.5% under their real rate.
Claude Code writes most of its cache with a 1-hour TTL, so this is not a
rounding error. On a 425-file corpus, `cli.py stats` moves from
$3379.26 to $3702.44 - a $323.18 (9.6%) increase in reported cost.
The API returns the split alongside the aggregate:
"usage": {
"cache_creation_input_tokens": 3904,
"cache_creation": {
"ephemeral_5m_input_tokens": 0,
"ephemeral_1h_input_tokens": 3904
}
}
Changes:
- scanner.py records the 1-hour portion in a new `cache_creation_1h_tokens`
column on `turns`, in both the main and sidechain parse paths. Existing
databases migrate in place via the existing `_ensure_column` helper.
- `cache_creation_tokens` still holds the aggregate, so token totals and
every existing display are unchanged. The 5-minute portion is derived as
the remainder, which keeps rows recorded before this change billing
exactly as they did.
- calc_cost / calcCost take an optional trailing 1-hour argument and derive
the 1-hour rate as 2x input rather than adding a rate to each of the two
duplicated pricing tables. Omitting the argument preserves the old
behaviour.
- Logs with no `cache_creation` object (older sessions) keep the whole
bucket on the 5-minute rate, and a breakdown claiming more 1-hour tokens
than the aggregate is clamped so it cannot inflate cost.
Adds six regression tests covering the 2x rate, the unchanged 5-minute
path, backwards compatibility when the argument is omitted, mixed TTLs,
and both malformed-breakdown clamps.
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.
Fixes #162.
What was wrong
Anthropic bills cache writes at 1.25x base input for the 5-minute TTL and 2x for the 1-hour TTL.
scanner.pyread only the aggregatecache_creation_input_tokens, and every cost path appliedcache_write— the 5-minute rate — to the whole bucket. 1-hour writes were therefore billed 37.5% under their real rate.Claude Code writes most of its cache with a 1-hour TTL, so this isn't a rounding error. The API returns the split alongside the aggregate:
Measured effect
Same 425-file corpus,
python cli.py scan && python cli.py stats, before and after:(The issue quotes a larger percentage against raw cache-write cost across all records; this is the smaller, more relevant figure measured through the tool's own deduplicated pipeline.)
Approach
The goal was to change pricing without disturbing token accounting or the two duplicated pricing tables.
cache_creation_tokensstill holds the aggregate. Only the 1-hour portion is new, in acache_creation_1h_tokenscolumn onturns. Every existing token total and display is untouched.1h = 0and therefore bill exactly as they did — the migration needs no backfill and no rescan.2 x inputrather than adding acache_write_1hentry to bothPRICINGtables. Those tables are duplicated betweencli.pyanddashboard.py(Pricing table duplicated across cli.py and dashboard.py — make Python the single source of truth #82), so deriving avoids adding a third place to keep in sync.calc_cost/calcCosttake an optional trailing argument. Omitting it bills everything at the 5-minute rate, so any caller not updated here behaves as before._ensure_columnhelper.Edge cases handled
cache_creationobject keep the whole bucket on the 5-minute rate.5m + 1hdoesn't reconcile.scanner.pyhas a second, more-indented assistant-record path for sidechain/subagent files that would otherwise have silently kept the old behaviour.Tests
153 passed(147 existing, unchanged, plus 6 new):Note on the tables
cache_writeremains the 5-minute rate in both tables and is untouched, so no existing entry changes meaning.