fix(ios): await progress observer completion instead of a 1s poll - #296
Conversation
testProgressObservationReleasesCompletedHandleAndCanRestart polled isProgressObservationRunning for 100 x 10ms after the progress request was counted. On a stalled hosted runner, delivering the 403 denial back to the observer took longer than that budget, so the observer was still running when the poll gave up, even though the handle is released correctly. Add waitForProgressObservation(), which awaits the current observer task (the same pattern as waitForDraftPersistence), and use it in place of the wall-clock poll. The test now asserts the exact request count after each phase, so a restart that never creates a fresh observer still fails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes This review covers the awaitable progress-observer seam and the affected iOS lifecycle and removal tests.
- Observer completion seam. Adds a main-actor method that awaits the current progress task without cancelling it or starting another observer.
- Test synchronization. Replaces fixed-duration polling in the two affected tests with task completion awaits and exact progress-request count assertions, including the restart case.
GPT Luna | 𝕏
Hermes Review BotConfidence: 5 Engine: SummaryEliminates a wall-clock polling flake in iOS chat progress observation tests on resource-constrained CI runners. Replaces the 100 × 10 ms (1 s) polling helper with an explicit Confidence Score: 5/5Full trace of task creation, execution, and cleanup on 📁 Important Files Changed
FindingsNo findings. Sequence DiagramUnchanged or not applicable for this change. []
|

Summary
AidenChatTests.testProgressObservationReleasesCompletedHandleAndCanRestartfailed on hosted CI for PR #272, which only touches desktop renderer code. Failing job: https://github.com/sambitcreate/aiden-agent/actions/runs/36760403852/job/110041412263The result bundle shows the failure was in the restart phase:
Timed out waiting for the progress observer to finish.(the wait at line 312)XCTAssertFalse failedat line 313progressRequestCount == 2assertion passed.So the second observer had made its request, but it was still running when the poll gave up.
Root cause
The production code has no race here. On a capability denial,
observeProgressreturns, andfinishProgressObservationreleases the handle inside the same task body.The problem was the test helper
waitForProgressObservationToStop. It polledisProgressObservationRunningfor 100 × 10 ms (about 1 s of wall-clock time), counted from when the stub URL protocol recorded the request. Several steps still happen after that point: the 403 is delivered through URLSession, the SSETaskreads the body, and the error hops back to the main actor. That runner was badly stalled; in the same run, tests that normally take milliseconds took 36 s and 39 s. Under that load the delivery took longer than the 1 s budget.Reproduced locally: I temporarily held the second
/progress/eventsresponse for 1.2 s after it was counted. The old test then failed with the same two messages as CI, and the fixed test passed.RequestDenied: TheFBSOpenApplicationServiceErrorDomain … RequestDeniedat 19:19 is a separate infrastructure problem. SpringBoard refused to launch the app on a parallel-testing clone ("Clone 2"). That was four minutes after this test had already run and failed on Clone 1, and it has no causal link to the assertion. Both are symptoms of the same overloaded host.Fix
AidenChatViewModel.waitForProgressObservation(). It awaits the current observer task without cancelling it, like the existingwaitForDraftPersistence(). The observer releases its handle inside the task, so after the await,isProgressObservationRunningreflects what the task body actually did, however slowly the host ran.testRemovalCancelsAdmittedConsumerBeforeHeldEventsPublishused the same wall-clock helper and now uses the same await. The helper is removed.Validation (local, Xcode beta, iPhone 17 Pro simulator)
-run-tests-until-failure, with 40 busy-loop processes saturating the 10-core host.AidenChatTestsclass passed: 226 tests, 0 failures.Android
This is iOS test synchronization plus a read-only await seam. No shared protocol, server contract, or transcript UI changed, so Android needs no change.
🤖 Generated with Claude Code