From 17bef75509e6fc0b8199fb19bba0ebdafb21c223 Mon Sep 17 00:00:00 2001 From: kirillk Date: Wed, 26 Aug 2026 09:28:41 -0400 Subject: [PATCH] fix(jetbrains): prune deleted sessions in the merged activity snapshot activitySnapshot() merged statuses and activity, two StateFlows that each prune the removed-session set through their own collector. After a delete, one flow can still carry the session while the other has already dropped it, so the snapshot briefly reported a deleted session as RUNNING and any consumer reading it at that moment rendered a stale badge. Subtract the removed set in the snapshot itself so the merged view is consistent regardless of collector ordering, and wait on both flows in the test that asserts every activity kind, which had the same latent race. This fixes the flaky KiloSessionServiceTest failure on main. --- .changeset/jetbrains-deleted-session-badge.md | 5 +++++ .../kotlin/ai/kilocode/client/app/KiloSessionService.kt | 6 +++++- .../kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt | 4 +++- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .changeset/jetbrains-deleted-session-badge.md diff --git a/.changeset/jetbrains-deleted-session-badge.md b/.changeset/jetbrains-deleted-session-badge.md new file mode 100644 index 00000000000..3697fecf5d2 --- /dev/null +++ b/.changeset/jetbrains-deleted-session-badge.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-jetbrains": patch +--- + +Stop showing a running badge for a session that was just deleted. diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt index 422859cd886..1c0708b5464 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/app/KiloSessionService.kt @@ -124,10 +124,14 @@ class KiloSessionService internal constructor( * Per-session activity for history and session lists. [activity] is the richer source — it also * carries waiting and failed sessions, and it covers sessions that are not open — but it drops * sessions whose directory the backend cannot resolve, so the busy statuses stay as a fallback. + * + * [statuses] and [activity] prune [removed] through separate collectors, so one can still carry + * a deleted session while the other has already dropped it. Subtracting [removed] here keeps the + * merged snapshot consistent instead of briefly badging a deleted session as running. */ internal fun activitySnapshot(): Map { val busy = statuses.value.filterValues { it.type == "busy" }.mapValues { SessionActivityKind.RUNNING } - return busy + activity.value.mapValues { it.value.kind.toKind() } + return (busy + activity.value.mapValues { it.value.kind.toKind() }) - removed.value } suspend fun list(dir: String): SessionListDto { diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt index dd1c2c89f54..ac9231cf653 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/app/KiloSessionServiceTest.kt @@ -151,7 +151,9 @@ class KiloSessionServiceTest : BasePlatformTestCase() { "ses_failed" to SessionActivityDto("/repo/wt", SessionActivityKindDto.ERROR), "ses_asking" to SessionActivityDto("/repo/wt", SessionActivityKindDto.QUESTION), ) - service.activity.first { it.isNotEmpty() } + // Both maps feed the snapshot through separate collectors, so wait for each one. + service.statuses.first { it.isNotEmpty() } + service.activity.first { it.size == 2 } assertEquals( mapOf(