Repository navigation
fix(#54): recompute context stats on in-place session refresh - #57
Merged
Merged
Conversation
refreshSessionInPlace replaced the conversation but never recomputed
context stats, so the Context pill (findLastTrackedAiGroupId) and per-turn
cost stayed frozen at fetch-time values for the rest of a long session.
Extract the Phase-2 block from fetchSessionDetail into a shared
runContextPhase2 and call it from refreshSessionInPlace as well, guarded
by a module-level Phase-2 epoch (sessionPhase2Generation) that mutually
invalidates fetch/refresh computations so stats are never written in
reverse conversation order.
TDD: new sessionDetailSlice.test.ts (4 tests). Pre-fix, the two refresh
tests failed with sessionContextStats.has('new-group') === false — stats
frozen at fetch values, exactly the issue symptom. Post-fix: green. All
three mutants killed: dropping the refresh call fails 3 tests; dropping
per-tab writes fails the per-tab test; dropping the epoch guard fails
the stale-run race test (stale run would leak the previous session's
group into the other session's stats map).
Fixes #54
Co-Authored-By: Claude Code <noreply@anthropic.com>
…erations Code-review of the first cut found a class of invalidation bugs: Phase-2 stats are per-session but stored in unkeyed fields, and generation-based guards kill or admit runs by the wrong criterion. Replace the Phase-2 epoch counter and the refresh-generation guard with write-time identity checks: a run commits only while the live conversation still IS the items array it computed over (global) and each target tab still shows that same conversation (per-tab, now one merged set()). - a no-op refresh (unchanged sentinel / fingerprint skip) no longer abandons the in-flight Phase-2 of the last commit — that left the just-committed conversation with stale stats forever (pinned by a new test with a deferred IPC mock and an `unchanged` sentinel response); - a Phase-2 racing a switch to another session (cached-tab swap or full fetch) drops its result instead of writing the old session's stats over it (race test retargeted from the epoch to the identity guard); - ssh guard hoisted into runContextPhase2; refresh no longer skips Phase-2 for an empty-items conversation (matches fetch semantics); - tests: drop dead TURN3 fixture and a leftover console.log, give each fixture session its own projectPath (module-level agent-configs cache is shared across tests). Mutations killed: removing the identity guard fails the race test (stale run leaks the previous session's group into the other session's map); restoring the refresh-generation guard fails the no-op test. Co-Authored-By: Claude Code <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.
Проблема
refreshSessionInPlaceзаменял conversation при дописывании JSONL, но не запускалпересчёт контекстной статистики (Phase 2,
processSessionContextWithPhases).sessionContextStats/sessionPhaseInfoзамерзали на fetch-овых значениях; плашкаContext через
findLastTrackedAiGroupIdвечно указывала на последнюю старуюгруппу — «Context 45k / $0.80» мог провисеть до конца сессии. Заморожена была и
стоимость хода — та же причина. Обе части закрываются одним фиксом.
Фикс
Phase-2 блок извлечён из
fetchSessionDetailв общийrunContextPhase2(set, get, { items, projectRoot, tabIds, isStale })(один источник истины), и refresh-путь теперь его вызывает.
Race-защита — module-level epoch
sessionPhase2Generation: каждый запускпересчёта инкрементирует его, in-flight запуски сбрасывают результат перед
каждой записью в store. Epoch взаимно инвалидирует fetch↔fetch, fetch↔refresh
и refresh↔refresh; без него stale-запуск мог бы записать статистику предыдущей
сессии поверх другой (этот сценарий пинится тестом). Отдельный дебаунс не
нужен: watcher уже троттлит (150мс-1с адаптивно), coalescing оставляет ≤1
refresh в полёте, а оба fast-path (unchanged-сентинел, chunk-fingerprint)
выходят до пересчёта.
SSH-режим скипается тем же гвардом, что и раньше.
TDD-доказательство
Новый
test/renderer/store/sessionDetailSlice.test.ts(4 теста). До фикса обаrefresh-теста падали с
sessionContextStats.has('c-ai-2') === false— статистиказаморожена, ровно симптом ишью. После — зелёные. Мутационные проверки (руками,
TS-мутирующего тула нет):
Fixes #54
🤖 Generated with Claude Code