chronod: a Live Activity ended right after a restart keeps reloading with "Could not find a descriptor"
Generated from repros/ios-chronod-dismissed-activity-reload-loop/README.md when the site was built at 7aa23e2.
What niso does
Section titled “What niso does”hosts/apple/Sources/SurfacePublisher.swift used to end every Live Activity an earlier run left, with
.immediate, as soon as the app launched. After a restart that triggers this bug. Now the app keeps a
leftover activity when the page still has an activity surface. When the page has none, the app ends
the leftover with .after(now + 15 s): an ended activity keeps loading until it is dismissed, so the
scenes SpringBoard restores get their content before the activity goes away.
Reproduce
Section titled “Reproduce”repro.sh [UDID] builds a two-file app with a widget extension (App.swift, Widgets.swift,
Attributes.swift), and then runs these steps on an iOS simulator. It exits 1 while the bug reproduces.
--loop also waits 12 minutes for chronod’s first scheduled retry.
- Launch the app with
-start. It requests a Live Activity (ActivityConfigurationshowing a timer). - Restart the device (
simctl shutdown,simctl boot). - Launch the app as soon as the device is up. On launch it ends every activity in
Activity<ReproAttributes>.activitieswithdismissalPolicy: .immediate.
Found with Xcode 27.0 (27A266a), iOS 27.0 (24A434) simulator, iPhone 17. It has not been tried on a device. On a device the same order happens when an app launches right after a reboot. It probably also happens right after an app update, but that has not been tried.
Expected
Section titled “Expected”The activity is ended and dismissed. chronod drops it, the same as when the app ends an activity
without a restart first: chronod logs Activity state changed to: dismissed and
Dropping subscription for dismissed activity.
Actual
Section titled “Actual”About ten seconds after the app ends the activity, chronod tries to load its content, fails, and schedules a retry that keeps repeating:
22:09:49.633 chronod [com.apple.chrono:activity] Adding descriptor for changed activity: E779BAE1-…; state: active22:10:00.184 chronod [com.apple.chrono:activity.store] Could not find a descriptor for E779BAE1-…22:10:00.188 chronod [com.apple.chrono:activity.store] [dev.niso.repro.liveactivity::dev.niso.repro.liveactivity.widgets:Attributes type: ReproAttributes:E779BAE1-…] reload: failed during preparation. - [w:fix-374.00-h:dyn-64.00-160.00-cr:23.50000-s:1.00000-….fam:medium]: ChronoCore.PlatterContentError(code: Activity had no descriptor., …)22:10:00.220 chronod [com.apple.chrono:activity] [E779BAE1-…] Ending activity because it failed to load its initial content repeatedly.liveactivitiesd has already discarded the activity, so chronod’s request to end it gets
Received an update for an activity with no subscribers. From then on, every ~10.7 minutes chronod
launches the widget extension and repeats the same lines for the same activity id (Reload failed; next attempt due at …,
scheduledRetry(externalRequest(… reason: payloadChanged …))), until chronod restarts.
Nothing shows on screen, because SpringBoard did dismiss the activity. The visible cost is the repeated
extension launches and error logs.
Sequence, from the unified log
Section titled “Sequence, from the unified log”- Boot. liveactivitiesd:
Restored activities: <id>. SpringBoard registers newActivityUIServicesandSystemAperturescenes for it. chronod:Adding descriptor for changed activity: <id>; state: active. - App launch, about 3 s later. The app ends the activity with
.immediate. liveactivitiesd:Ending activity <id> for XPC participant content source process(<app>), thenActivity discarded. chronod’s ActivityKit client logsActivity dismissed: <id>andRemoving dismissed ephemeral activity request. But chronod has no subscription for the activity yet, so it logs neitherActivity state changed to: dismissednorDropping subscription. - About 9 s later, the scene SpringBoard restored in step 1 subscribes:
Subscribing to activity: [<app>::<extension>:Attributes type: …:<id>]. chronod treats this as an initial load. It asks ActivityKit for the activity’s descriptor (Fetched descriptors: [], since the activity is gone) and fails withCould not find a descriptorthree times. - chronod gives up (
Ending activity because it failed to load its initial content repeatedly). But the reload task stays scheduled, and it retries the dead activity every ~10.7 minutes.
The extension’s own Live Activity descriptor is fine: chronod’s database (Library/chronod/chrono.sql, table
Descriptors) holds a CHSLiveActivityDescriptor with the right attributesType, and new activities load.
Without the restart, ending the activity at launch is clean. With the restart, ending it with .after(now + 15 s)
instead of .immediate is also clean: the restored scene loads the ended activity’s content (Content load successful),
and when it is dismissed chronod logs Dropping subscription for dismissed activity.
Feedback text
Section titled “Feedback text”chronod keeps reloading a Live Activity that was ended before SpringBoard’s restored scene subscribed to it
After a restart, if an app ends a restored Live Activity with
.immediatebefore SpringBoard’s restored activity scenes subscribe, chronod later starts an initial content load for the dead activity. It logs “Could not find a descriptor for” / “Activity had no descriptor”, tries to end the activity, and then retries every ~10.7 minutes, each time launching the widget extension, until chronod restarts. The attached project reproduces it on the iOS 27.0 simulator (Xcode 27.0): repro.shstarts an activity, restarts the simulator, launches the app (which ends leftover activities at launch), and prints chronod’s log.Expected: chronod drops a dismissed activity even when no subscription exists yet, and ignores later subscriptions to it, as it does when no restart happened.
Not filed yet.