Symptom

Kraken’s nightly architectural scan flagged GraphScreen.kt and DiagramScreen.kt for “unstructured concurrency”: while (isActive) { delay(5_000); ... } polling loops inside LaunchedEffect were claimed to lack lifecycle-bound cancellation, allegedly leaking coroutines on rapid navigation because “LaunchedEffect only cancels on key change, not on composition exit in all cases.” The lead also claimed DiagramScreen.kt had no polling loop.

Root cause

False positive. LaunchedEffect’s coroutine is a structured child of the Composition: the Compose runtime cancels it both when its keys change (relaunching fresh) and unconditionally when the composable leaves composition — that dual cancellation is the entire point of LaunchedEffect as a lifecycle primitive, not an edge case that fails under rapid navigation.

Fix

No code change was needed. The existing polling loops in GraphScreen.kt and DiagramScreen.kt are already correctly lifecycle-scoped via LaunchedEffect.

Prevention

If Kraken files a coroutine-lifecycle lead against a while (isActive) { delay(...); ... } loop, check first whether it’s nested inside LaunchedEffect (or another Compose-runtime-owned scope like pointerInput’s awaitPointerEventScope) before treating it as unstructured — that placement already provides composition-scoped cancellation on both key change and disposal. Only flag loops that run inside a manually constructed CoroutineScope(...) or GlobalScope. Same class of false positive as #924 (docs/lessons/2026-07-21-nodeitem-graphscreen-coroutine-lifecycle-false-positive.md).