Found while fixing #4643 (PR #4647).
Problem
buildCacheKey in src/node/utils/main/tokenizer.ts keys the token-count LRU and in-flight map by model:CRC32(text):text.length. Distinct texts of the same length collide: the remote UAT found 17 colliding texts in a 1.24M-row synthetic chat. A colliding text reuses another text's count.
Before #4647 every submission happened in one synchronous turn, so the same history always produced the same (slightly wrong) total. With the calculation now interleaving, which text claims a shared key first depends on timing, so totals differ slightly between runs and between concurrent calculations (UAT: 962 vs 946 vs 972 on a small scale; locally 68,099,845 vs 68,099,879 of 68M). The error is ~1e-7, but the persisted cache can store either value.
Fix direction
Use a collision-resistant key (e.g. a 64-bit/128-bit hash, or the text itself for short strings) and keep the LRU memory bounded.
Generated with xum • Model: anthropic:claude-opus-5-5 • Thinking: high • Cost: $9.90
Found while fixing #4643 (PR #4647).
Problem
buildCacheKeyinsrc/node/utils/main/tokenizer.tskeys the token-count LRU and in-flight map bymodel:CRC32(text):text.length. Distinct texts of the same length collide: the remote UAT found 17 colliding texts in a 1.24M-row synthetic chat. A colliding text reuses another text's count.Before #4647 every submission happened in one synchronous turn, so the same history always produced the same (slightly wrong) total. With the calculation now interleaving, which text claims a shared key first depends on timing, so totals differ slightly between runs and between concurrent calculations (UAT: 962 vs 946 vs 972 on a small scale; locally 68,099,845 vs 68,099,879 of 68M). The error is ~1e-7, but the persisted cache can store either value.
Fix direction
Use a collision-resistant key (e.g. a 64-bit/128-bit hash, or the text itself for short strings) and keep the LRU memory bounded.
Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high• Cost:$9.90