cmux-next: FolderChanges arms its watches before events() returns (fixes BackdropCatalogStoreTests hang)
Fixes a flake in the cmux-next swift test job (seen in job 122287803089, the full parallel CmuxNextAppTests run):
ā Test theCatalogIsBuiltOnceAndRebuiltOnlyWhenAFolderChanges() recorded an issue at BackdropCatalogStoreTests.swift:28:6: Time limit was exceeded: 60.000 seconds
ā Test theCatalogIsBuiltOnceAndRebuiltOnlyWhenAFolderChanges() recorded an issue at BackdropCatalogStoreTests.swift:46:9: Expectation failed: store.catalog.choices.contains { $0.id.contains("mine.png") }
Cause: FolderChanges.events() returned its stream and armed the folder watches (the DispatchSource file-system sources) later, inside a .utility detached task. BackdropCatalogStore calls events(), builds the catalog and bumps builds. The test waits for that build and then adds mine.png. Under a loaded parallel run, the utility task could start only after the file was added. That change was never seen, so the wait for a second build never ended and the 60 s limit fired. The test already waits on a condition (an observed build counter) and not on a sleep. The missing event was the bug, so the time limit stays as it is.
Change: events() now arms the watches before it returns, and the loop task takes them over. So any change made after events() returns is buffered and seen. FolderChangesTests drops the 100 ms head start it gave the loop to arm, which this makes unnecessary.
Proof (fleet suite gate): at head 90b418c, BackdropCatalogStoreTests passed 5/5 and FolderChangesTests passed 5/5 (no head-start sleep). The suite gate runs one suite per process, so the load that starved the utility task in the full parallel run does not happen there. The base red is only in CI's parallel run, and at base 68f7028 the gate passes 5/5 too. The fix rests on the ordering argument above, not on a reproduction.
No capture: nothing visible changes. This is the timing of a folder watch, and the wallpaper list looks the same before and after.
Review: Codex (gpt-6-astra, xhigh) gave SHIP at head 90b418c. It checked that the non-Sendable FolderWatch moves into Task.detached under region isolation.
Risk: low. The watch opens a few microseconds earlier, on the caller's thread rather than the loop's. Cancellation and re-arming are unchanged. The only other user is BackdropCatalogStore.
Agent session: agent session
š¤ Generated with Claude Code