In compact(), the budget loop calls analyzePayload(out, { counter }) in its own condition:
while (analyzePayload(out, { counter }).totalTokens > maxTokens) {
...
list.splice(idx, 1);
}
Every iteration re-walks and re-counts the entire payload, so trimming a long transcript is quadratic in the number of messages dropped. On a real Claude Code session with a few hundred messages this is the slowest thing the tool does, and it is doing work it already has.
The counts are additive, so the loop can track the running total instead: compute the total once, and subtract the tokens of each message as it is spliced out.
Acceptance
compact produces byte-identical output to today for the existing tests
analyzePayload is called at most twice in compact (the before and after reports)
- A test with a payload of ~500 small messages and a low
maxTokens completes without the loop being the bottleneck
Good first issue: the change is local to one function and the existing tests pin the behaviour.
In
compact(), the budget loop callsanalyzePayload(out, { counter })in its own condition:Every iteration re-walks and re-counts the entire payload, so trimming a long transcript is quadratic in the number of messages dropped. On a real Claude Code session with a few hundred messages this is the slowest thing the tool does, and it is doing work it already has.
The counts are additive, so the loop can track the running total instead: compute the total once, and subtract the tokens of each message as it is spliced out.
Acceptance
compactproduces byte-identical output to today for the existing testsanalyzePayloadis called at most twice incompact(the before and after reports)maxTokenscompletes without the loop being the bottleneckGood first issue: the change is local to one function and the existing tests pin the behaviour.