A SensorKit fetch that returns nothing is usually asking for the wrong window

22 September 2026

The fetch completes. sensorReader(_:didCompleteFetch:) is called. sensorReader(_:fetching:didFetchResult:) never is, and sensorReader(_:fetching:failedWithError:) never is either. There is no error to look up, because as far as the framework is concerned the request succeeded and matched nothing.

Almost always the request asked for a window that cannot contain data. The window has an edge at each end.

The recent edge: the 24-hour hold

SensorKit keeps newly recorded data to itself for 24 hours before any app can read it, so the user has a chance to delete what they would rather not share. That much is documented on SRFetchRequest, along with the part that surprises people:

Important: A fetch request returns no results at all if its time range overlaps the holding period. The range is not trimmed to the part that has aged past the hold. One hour of overlap and the whole request comes back empty.

So to: Date(), the obvious thing to write and the thing every first implementation writes, can never return a sample. Neither can "the last hour", or "today". The most recent moment worth asking for is 24 hours ago, and asking for anything newer costs the entire result, not the tail of it.

The far edge: before your app was authorized

An app cannot fetch anything from before the moment the user authorized it. That is the floor, it is per app, and nothing moves it.

This is the edge the documentation makes easy to misread. SRSensorReader says an app "has access to 7 days of prior recorded data for an active sensor", which reads like a promise that a device already recording for another app or for the system will hand you a week of history as soon as you are authorized. It will not. The sensor may have been running for months and the samples may be sitting on the device, but your window opens at your own authorization and not one second earlier. A from behind that point does not return a truncated result. It returns nothing.

Design the onboarding for that. An app that asks for a week of history on the day the user consents has nothing to show them, and the emptiness looks identical to a bug.

The same floor is what makes a development cycle painful. Deleting the app and reinstalling resets its authorization, and therefore resets the floor; installing a new build over the old one does not. A workflow built on delete-and-reinstall starts every test run at the bottom of a fresh 24-hour hole, usually with a from that predates the new authorization and a to inside the hold. Both edges are wrong at once, and there is no error either way.

Writing a request that can succeed

Neither edge has a switch. Both are privacy guarantees to the user. The hold gives them time to delete, and the floor means consent applies from the moment it is given, not retroactively. There is no entitlement or debug setting that shortens either one. The testing schedule has to absorb them, and so does anything you promise a study participant on their first day.