AppStore.sync Replays the Latest Subscription Renewal into Transaction.unfinished on iOS 26.4 Sandbox

StoreKit2 Repro Notes: the latest renewal appears in Transaction.unfinished after restore (2026-04-05)

1. Issue Summary

In the current project, during a normal cold launch:

  • Transaction.latest(for:) returns a value for the weekly subscription
  • Transaction.all returns the full subscription history chain
  • Transaction.unfinished is empty

However, after tapping Restore Purchases and calling AppStore.sync(), one "latest renewal" transaction appears in Transaction.unfinished.

This behavior looks more like a system-side replay triggered by AppStore.sync() than a consistently unfinished transaction during a normal launch.

2. Affected Product

Product:

  • do.i.iapc.vip.week

Transaction chain characteristics:

  • All transactions belong to the same auto-renewable subscription chain
  • originalTransactionID = 2000001143446796
  • The transaction that appears in unfinished is usually the latest or last renewal in the chain

3. Current Code Path

During app startup:

  1. loadProducts()
  2. Debug snapshot for Transaction.latest(for:)
  3. Debug snapshot for Transaction.all
  4. Scan Transaction.unfinished
  5. refreshEntitlements()

During restore purchases:

  1. Call AppStore.sync()
  2. Scan Transaction.unfinished
  3. refreshEntitlements()

4. Preconditions

  • A Sandbox test account is used
  • The weekly subscription do.i.iapc.vip.week already has multiple historical renewal transactions
  • The subscription is already expired, so entitlements = 0 during a normal launch
  • The issue is easier to reproduce on an iOS 26.4 device
  • The issue was not consistently reproduced on another iOS 18.2 device

5. Reproduction Steps

Path A: Normal cold launch

  1. Launch the app
  2. Observe the logs:
    • LatestTransaction snapshot
    • AllTransaction snapshot summary
    • unfinished processing result

Observed result:

  • latest has a value
  • all contains the full history chain
  • unfinishedHandledCount = 0

Path B: Tap Restore Purchases

  1. Launch the app
  2. Tap Restore Purchases
  3. Trigger AppStore.sync()
  4. Observe the logs:
    • restore started
    • unfinished processing started
    • unfinished transaction received

Observed result:

  • After restore, one "latest renewal" transaction appears in unfinished
  • That same transaction does not necessarily appear during a normal cold launch

6. Expected Result

If a transaction has already been successfully finished in the past, it should not appear again as unfinished after Restore Purchases.

A stricter expectation is:

  • During a normal cold launch, unfinished = 0
  • After tapping Restore Purchases, unfinished should still remain 0

7. Actual Result

Actual behavior:

  • Normal cold launch: unfinished = 0
  • After Restore Purchases: one "latest renewal" transaction appears again in unfinished

This suggests that AppStore.sync() may replay the most recent historical subscription transaction.

8. Current Assessment

Based on the current logs, the issue is more likely to be:

  • Related to AppStore.sync() / StoreKit / Sandbox replay behavior on the system side
  • Easier to reproduce on iOS 26.4
  • Less likely to be caused by a persistent app-side bug where finish() is missed during a normal startup flow

Reasons:

  • During a normal launch, unfinished = 0
  • The behavior is inconsistent across devices and OS versions, even with the same Sandbox account
  • latest, all, and unfinished can be clearly separated during a normal cold launch

9. Suggested Engineering Position

Suggested wording for internal or external communication:

In the iOS 26.4 + Sandbox environment, calling AppStore.sync() may cause StoreKit to replay the latest historical subscription transaction into Transaction.unfinished. Since the same transaction does not necessarily appear during a normal cold launch, the issue currently looks more like a system/environment-specific behavior difference than an app-side bug where finish() is consistently missed during the regular startup path.

10. Additional Evidence That Can Be Collected

If this needs to be escalated to the team or to Apple, the following would strengthen the report:

  • Full log comparison before and after tapping Restore Purchases
  • The same transactionId compared between normal launch and post-restore behavior
  • Cross-device comparison on different iOS versions
  • A minimal reproducible sample project and Sandbox test record
AppStore.sync Replays the Latest Subscription Renewal into Transaction.unfinished on iOS 26.4 Sandbox
 
 
Q