E4 regression: activity-manifest resource() blocks whenStable() — fundraiser + calendar suites hook-timeout on master #733

Closed
opened 2026-07-20 14:34:33 +00:00 by spikerj · 1 comment
Owner

Master CI run 2264 (run 11111, commit e381d4bc) fails test-and-lint: feature-fundraiser:test (21 tests, "Hook timed out in 10000ms") and spikersoft:test (3 calendar tests, 15000ms). Reproduced locally on clean master.

Root cause (verified by experiment): E4 PR4 (c7a23af5) migrated ActivityManifestService's fetch to a gated resource(). A loading resource registers a PendingTask, which blocks ApplicationRef.whenStable() app-wide. The default ACTIVITY_AUTH_GATE factory is () => true, so in any TestBed that doesn't override the gate:

component → injects ActivityTrackingService → constructor calls ensureLoaded() → gate open → resource fires GET /activity/manifestprovideHttpClientTesting() parks the request unflushed → pending resource never settles → await fixture.whenStable() in beforeEach deadlocks → hook timeout.

Both failing specs await fixture.whenStable() and don't stub the gate. Pre-E4 the fetch was a raw promise held in a service field, which does not block stability. Closing the gate in the fundraiser spec → 93/93 pass in 1s (was 21 failed / 210s) — confirms the mechanism.

Why CI didn't catch it on the PR: needs confirmation, but consistent with the tests.yml path-filter gap already tracked in the tracker (service change didn't trigger dependents' suites; the full run on master push did).

Fix direction: revert the manifest fetch trigger from resource() to the in-flight-promise pattern. Nothing consumes the resource reactively (its deferred resolves inside the loader) — it's fire-and-forget background cache refresh, i.e. the same "shape doesn't fit → stay imperative" carve-out E4 PR4 itself applied to photograph/geo-name/scheduled-tasks. Also a production concern, not just tests: anything awaiting app stability (whenStable-based logic) stalls on a telemetry fetch the user never asked for.

Master CI run 2264 (run 11111, commit e381d4bc) fails `test-and-lint`: `feature-fundraiser:test` (21 tests, "Hook timed out in 10000ms") and `spikersoft:test` (3 calendar tests, 15000ms). Reproduced locally on clean master. **Root cause** (verified by experiment): E4 PR4 (c7a23af5) migrated `ActivityManifestService`'s fetch to a gated `resource()`. A loading resource registers a PendingTask, which blocks `ApplicationRef.whenStable()` app-wide. The default `ACTIVITY_AUTH_GATE` factory is `() => true`, so in any TestBed that doesn't override the gate: component → injects `ActivityTrackingService` → constructor calls `ensureLoaded()` → gate open → resource fires GET `/activity/manifest` → `provideHttpClientTesting()` parks the request unflushed → pending resource never settles → `await fixture.whenStable()` in `beforeEach` deadlocks → hook timeout. Both failing specs await `fixture.whenStable()` and don't stub the gate. Pre-E4 the fetch was a raw promise held in a service field, which does not block stability. Closing the gate in the fundraiser spec → 93/93 pass in 1s (was 21 failed / 210s) — confirms the mechanism. Why CI didn't catch it on the PR: needs confirmation, but consistent with the tests.yml path-filter gap already tracked in the tracker (service change didn't trigger dependents' suites; the full run on master push did). **Fix direction**: revert the manifest fetch trigger from `resource()` to the in-flight-promise pattern. Nothing consumes the resource reactively (its deferred resolves inside the loader) — it's fire-and-forget background cache refresh, i.e. the same "shape doesn't fit → stay imperative" carve-out E4 PR4 itself applied to photograph/geo-name/scheduled-tasks. Also a production concern, not just tests: anything awaiting app stability (whenStable-based logic) stalls on a telemetry fetch the user never asked for.
Author
Owner

Verified complete: spikersoft-angular PR #464 merged — manifest fetch reverted to an in-flight promise, un-blocking whenStable() app-wide. Closing.

Verified complete: spikersoft-angular PR #464 merged — manifest fetch reverted to an in-flight promise, un-blocking whenStable() app-wide. Closing.
Sign in to join this conversation.