Unified Log retention on a busy vs idle iPhone (iOS 26) – Thesis Friday #28

Key finding

Unified Log retention is not a fixed number of hours. On an iPhone in daily use the kernel entries of an unlock were gone within 13.2 hours, while on an idle reference phone they were complete after 87.8 hours. Only the ChronoServices keybag lines, which carry a time-to-live, survived on the busy phone, and the oldest event in the archive said nothing about what had been lost.

Interlude in the Anchored Log Reconstruction series. Method page: thesisfriday.com/alr

In my graduation thesis I wrote that physical interactions disappear from the Unified Log within twenty-four hours. In #23 I put user-interaction artefacts at roughly ten to twenty-four hours. This post tests both statements against a controlled run on two phones.

Neither holds as a general figure. On a phone in daily use, the kernel lines of an unlock were gone within thirteen hours. On an idle reference phone, the same lines were still complete after almost four days. The only lines that survived on the busy phone were the ones the method treats as the weakest evidence.

The run

Two iPhones on the same build, iOS 26.6.2 (23G90). The iPhone 12 is a factory-reset reference device, with no SIM and one test User Apple ID. The iPhone 14 is in daily use with a SIM and an Apple ID.

On 14, 15 and 17 September I ran the same block of fifteen steps on both phones. Each block took just over two minutes. It contains three Face ID unlocks, one failed Face ID attempt, one passcode unlock, two presses on each volume button, and locks in between. Every step was marked with a python script against an external clock reference.

I collected both phones three times with log collect over USB:

sudo log collect --device-udid 00008101-000111D226****** --output iphone12_T*-cli.logarchive

sudo log collect --device-udid 00008110-0016559202****** --output iphone14_T*-cli.logarchive

The first collection was on the evening of 14 September (T0), the second on the evening of 17 September (T3), and the third on the morning of 18 September (T4). That gives each block several ages:

BlockT0T3T4
14 September2.3 h75.8 h87.8 h
15 September–50.0 h62.1 h
17 September–1.2 h13.2 h

The T4 ages are measured from the last line in each archive, which falls just before the collection started.

What a family is

A single unlock produces hundreds of log lines, written by different processes at different levels of the system. To compare collections, I did not count “unlocks”. I counted families.

A family, in this post, is a group of lines that record the same step of an unlock and can be selected with one predicate. The same predicate returns the same kind of line in every archive. That makes it possible to say “this set of lines was there at 1.2 hours and gone at 13.2 hours” without first deciding what counts as one event.

Five families, from the bottom of the stack to the top:

Handle_async_keybag_unlockeventMessage CONTAINS “handle_async_keybag_unlock”
Volume notificationeventMessage CONTAINS “Sending notification for volume”
ChronoServices keybag subsystem == “com.apple.chrono” AND category == “keybag”
Pearl camera frameseventMessage CONTAINS “PearlCamFrameReceived”
Biometrickitd process== “biometrickitd”

The first two are kernel lines. They record that the keybag was unlocked and that the data volume was notified. “Volume” here means the storage volume, not the volume buttons. In #27 I argued that these two carry the provable endpoint of an unlock.

The ChronoServices line records the same transition from locked to unlocked, but higher in the stack. Four processes write it: chronod, SpringBoard, duetexpertd and WidgetRenderer_Default. One transition therefore yields about four lines, which is why #27 says it cannot carry the endpoint on its own.

The last two families belong to Face ID: the frames from the infrared camera, and the daemon that handles the match.

A family count is a number of lines, not a number of unlocks. Five unlock-related steps in a block produce 10 to 14 lines in the first family and several hundred in the last.

For each block I counted the lines of each family between its first and last logbook marker, rounded outward to whole seconds.

Results

Before looking at the 13-hour collection, I fixed what would count as loss: fewer lines in the two kernel families at T4 than at T3, with the iPhone 12 unchanged.

iPhone 14, in daily use

Family14 Sep, 2.4 h or more14 Sep, 75.8 h14 Sep, 87.8 h15 Sep, 50.0 h15 Sep, 62.1 h17 Sep, 1.2 h17 Sep, 13.2 h
handle_async_keybag_unlock120000120
volume notification100000100
ChronoServices keybag280027273838
Pearl camera frames1080000960
biometrickitd59100004170

iPhone 12, idle reference

Family14 Sep, 2.3 h14 Sep, 75.8 h14 Sep, 87.8 h15 Sep, 50.0 h15 Sep, 62.1 h17 Sep, 1.2 h17 Sep, 13.2 h
handle_async_keybag_unlock10101010101414
volume notification777771111
ChronoServices keybag3535039394242
Pearl camera frames630630630126126210210
biometrickitd556556556481481501501

On the busy phone, the block from 17 September lost both kernel families, the camera frames and biometrickitd within 13.2 hours. Only the ChronoServices lines remained, all 38 of them. The block from 15 September showed the same pattern at 50 and 62 hours: 27 ChronoServices lines and nothing else.

On the idle phone, four of the five families were unchanged at every age, up to 87.8 hours. The exception was ChronoServices in the oldest block, which went from 35 lines to none between 75.8 and 87.8 hours. I come back to that below.

These are counts per family, not a line-by-line comparison.

Time-to-live, per family

Lionel Notari showed in The Myth of 30 Days Retention that most events carry a time-to-live of 0, between 69.6 and 89.6 percent across seven archives. He also showed that the TTL groups in log stats are ranges. He left open which time-to-live the lines from a user action carry.

Counted with log stats and an explicit timeToLive predicate on the T3 archives:

FamilyiPhone 12iPhone 14
handle_async_keybag_unlock206 of 206 at TTL 0234 of 234 at TTL 0
volume notification199 of 199 at TTL 0217 of 217 at TTL 0
Pearl camera frames18,108 of 18,108 at TTL 014,048 of 14,048 at TTL 0
biometrickitd98.4% at TTL 096.0% at TTL 0
ChronoServices keybag576 of 576 with a time-to-live; TTL 5 in both windows measured3,342 of 3,342 at TTL 4 or higher

On the busy phone, every family without a time-to-live was gone from the block within 13.2 hours. The one family with a time-to-live was still complete.

For the ChronoServices predicate, log stats printed no TTL breakdown at all. Only an explicit timeToLive predicate showed that every line carried one. A missing section in a summary is not a zero.

As a check on the counting, I added up my log show counts per TTL value into the log stats groups for the same archive. The two methods agree to the event in every group.

On Notari’s basis (the log stats total, minus the TTL groups), the share of TTL 0 events was 85.0 and 84.7 percent on the idle phone at T3 and T4. That is inside his range. On the busy phone it was 64.1 and 60.7 percent, below it. A TTL 0 share describes what is still in the archive, not what was written.

Time-to-live is not an expiry date

On the idle phone I counted the exact time-to-live of the ChronoServices lines in two windows of the T3 archive. The 35 lines from the 14 September block and the 73 lines from 12 September all carry a time-to-live of 5, meaning five days, or 120 hours.

The lines from 14 September were gone when that block was at most 87.8 hours old, before their time-to-live had passed. The lines from 12 September were still present at 123 hours, and the oldest of them at 135 hours, after theirs had passed. Same family, same value, and one set disappeared early while the other stayed late.

On this device, for this family, the time-to-live does not tell you when a line leaves the archive. It tells you which lines have one. Notari was careful not to treat the value as a retention period. This measurement shows why that caution is needed.

The archive still looked deep

The busy phone’s archive began on 2 September at 00:30:19.646750, at both T0 and T3. The oldest event had not moved, and the 14 September block was gone. On that phone, loss did not run from old to new.

On the idle phone it was the other way around. Its oldest event moved from 27 August to 1 September between T0 and T3, and the recent blocks were untouched.

The oldest event in an archive does not tell you whether a given moment is still covered. #29, on the stop rule, builds on this.

The order of loss matters

In #27 I argued that the provable endpoint of an unlock sits in the kernel, and that the ChronoServices transition cannot carry it on its own because four processes write it. On the busy phone, the kernel lines had no time-to-live and were gone within thirteen hours. The ChronoServices lines had one and stayed.

An older archive from a device in daily use can hold the weaker evidence and not the stronger. Ordering by evidential strength, the principle from #22, applies to what the archive still contains, not only to how you read it.

What this replaces

My thesis states that physical interactions are gone from the log within twenty-four hours. That is not a general figure. On the busy phone, the kernel lines of a block were gone within 13.2 hours, which is faster. On the idle phone, the same families were still complete after 87.8 hours, which is far slower. The thesis devices were factory-reset test phones, much closer to the idle case. The more likely explanation for the thesis figure is the acquisition. That data came from the logarchive inside a sysdiagnose, and Christian Peter has shown that it holds fewer logs than a direct collection. I have not tested that explanation.

The ten to twenty-four hours in #23 falls for the same reason. That post now carries a correction block, and the change is dated on the method page.

My thesis also attributes to Notari a finding that 70 percent of artefacts are overwritten within twenty-four hours. He did not publish that. His figure is the share of events with a time-to-live of 0.

What this means at acquisition and in analysis

This run was not designed to test acquisition procedure, so this section stays close to what it measured. It describes what I would do, not a standard.

At acquisition

Collect early from a device that was in use. On the busy phone the kernel lines of an unlock were present after 1.2 hours and gone after 13.2. For a device that was in daily use, the window for the strongest unlock evidence is hours, not days. Whether a seized device keeps losing lines at the same rate once nobody uses it, I have not measured. That is the next question worth a run.

Record the state of the device at acquisition: in use or idle, on a charger or not, SIM and accounts present. In this run that state determined more than the build or the time-to-live.

Record the acquisition moment when you acquire, not when you register the archive afterwards. This week two of my age calculations were off by hours because I took the time from the wrong place. Every age in the analysis depends on that moment.

Collect the log in full. A start date in the collection tool limits what you get, and I have not found any trace of that limit in the resulting archive. #29 works this out. Christian Peter has shown that the log inside a sysdiagnose holds less than a direct collection.

In analysis

Before you reason about an absence, establish per family that the lines still exist in your window. The oldest event in the archive does not establish that.

Report which families you found, not only that you found an unlock. In an older archive from a busy device, the ChronoServices transition may be all that is left. That line shows a keybag transition, but on its own it does not carry the endpoint.

Do not read a time-to-live as a date. It tells you that a line has a lifetime, not when it leaves the archive.

How to record the checks you did, including the ones that found nothing, is the subject of #29.

Limits

Two iPhones, one build, three blocks. Five families, all tied to unlocking. The volume button presses were in the blocks but were not counted. The comparison is per family count, not line by line. The iPhone 14 was not factory reset, and its use during the run was not controlled. The T4 ages are taken from the end of each archive. The time-to-live values for ChronoServices were measured in two windows on one phone.

None of these hours is a retention figure for a case device. What transfers is the check: on a device in daily use, establish per family that the lines still exist in your window before you reason about their absence, and do not read a time-to-live as a date.

The reasoning transfers. The numbers do not.

Reference setup

iPhone 12 (iPhone13,2), iOS 26.6.2, build 23G90. Factory reset, no SIM, no Apple ID, permanently on a charger.

iPhone 14 (A2882), iOS 26.6.2, build 23G90. SIM, Apple ID, daily use.

Blocks on 14, 15 and 17 September 2026, recorded with reference_logbook.py against an external clock reference. Collections on 14 September (T0), 17 September (T3) and 18 September 2026 (T4) with sudo log collect --device-udid [UDID device] --output [filename].logarchive. Counts with log show and log stats on macOS 26.6.2. No case data was used.

© 2026 Tim Korver — Thesis Friday. Licensed under CC BY-NC-ND 4.0. Method: Anchored Log Reconstruction (ALR). Commercial or training use requires written permission. See Copyright and Use.

Monthly digest of new posts. Double opt-in, unsubscribe anytime. Privacy