Problem
clearAll() in LLMExtension.scala clears messageHistory, agentUsage, runUsage and agentProfile under their locks, but nothing invalidates calls that are still in flight. Start llm:chat-async on the observer, call clear-all, then let the reply arrive: its completion callback runs recordUsage and commitExchange, so the previous run's tokens and exchange land in the freshly reset state. The separate history and usage locks also mean a reset can split those two updates.
Found by a Codex review on 2026-09-08. Predates today's changes for history (since #44); token usage (#78) extends the same gap.
Fix
Capture a run generation number when a request is launched; advance it in clearAll(); have recordUsage and commitExchange drop the update when the generation no longer matches. One counter under one lock keeps history and usage consistent.
Test
Start a __TEST_DELAY: async call, clear-all, resolve it with runresult, then assert llm:history is [] and llm:get llm:usage-total "calls" is 0.
Problem
clearAll()inLLMExtension.scalaclearsmessageHistory,agentUsage,runUsageandagentProfileunder their locks, but nothing invalidates calls that are still in flight. Startllm:chat-asyncon the observer, callclear-all, then let the reply arrive: its completion callback runsrecordUsageandcommitExchange, so the previous run's tokens and exchange land in the freshly reset state. The separate history and usage locks also mean a reset can split those two updates.Found by a Codex review on 2026-09-08. Predates today's changes for history (since #44); token usage (#78) extends the same gap.
Fix
Capture a run generation number when a request is launched; advance it in
clearAll(); haverecordUsageandcommitExchangedrop the update when the generation no longer matches. One counter under one lock keeps history and usage consistent.Test
Start a
__TEST_DELAY:async call,clear-all, resolve it withrunresult, then assertllm:historyis[]andllm:get llm:usage-total "calls"is 0.