Skip to content

Recreate blobs directory after store schema migration#366

Merged
kean merged 1 commit into
kean:mainfrom
rusik:fix/migration-recreate-blobs-dir
May 17, 2026
Merged

Recreate blobs directory after store schema migration#366
kean merged 1 commit into
kean:mainfrom
rusik:fix/migration-recreate-blobs-dir

Conversation

@rusik
Copy link
Copy Markdown
Contributor

@rusik rusik commented May 14, 2026

Problem

When LoggerStore is initialized against an existing store whose manifest.json version differs from currentStoreVersion, removePreviousStore(at:) is invoked to wipe the previous data. It removes storeURL entirely and recreates it as an empty directory — but does not recreate the blobs/ subdirectory. The blobs/ directory was created earlier in init (via Files.createDirectoryIfNeeded(at: blobsURL)), and after removePreviousStore it no longer exists for the rest of the session.

For every blob captured during this session whose compressed size exceeds Configuration.inlineLimit (16 KB by default — i.e. almost any real-world JSON response body), storeBlob falls through to:

try? processedData.write(to: makeBlobURL(for: key.hexString))

The write silently fails because the parent directory is missing. The LoggerBlobHandleEntity row is still saved (with inlineData == nil, no file on disk), and responseBodySize is populated on NetworkTaskEntity. The console then shows:

Unavailable
The response body was deleted from the store to reduce its size. Increase responseBodySizeLimit of the store.

The error message is misleading — responseBodySizeLimit had nothing to do with it; bumping it doesn't help.

Reproduction

Empirical reproduction on iOS 26 simulator with a host app integrating Pulse:

  1. Use Pulse 5.1.4 (currentStoreVersion = 3.6.0). Capture a network response > 16 KB compressed. Confirm a file exists at current.pulse/blobs/<sha1> matching the ZLOGGERBLOBHANDLEENTITY.ZKEY row.
  2. Upgrade to Pulse 5.2.1 (currentStoreVersion = 3.7.0). Relaunch the app without uninstalling (so the old manifest.json is present). Make another large request. Observe:
    • manifest.json is now version: \"3.7.0\" (migration ran)
    • blobs/ directory exists but is empty
    • ZNETWORKTASKENTITY has the task with non-zero ZRESPONSEBODYSIZE and a non-null ZRESPONSEBODY FK
    • The referenced ZLOGGERBLOBHANDLEENTITY row exists, but ZINLINEDATA is NULL and no file exists at blobs/<hex(ZKEY)>
  3. PulseUI shows "Unavailable" for the response body.

Confirmed end-to-end on a real app upgrade path — this is what users will hit when bumping their dependency from 5.1.x to 5.2.x.

Fix

Mirror the cleanup pattern already used by _removeAll() (LoggerStore.swift:862-863) and recreate the blobs/ subdirectory at the end of removePreviousStore:

private static func removePreviousStore(at storeURL: URL) throws {
    try Files.removeItem(at: storeURL)
    try Files.createDirectory(at: storeURL, withIntermediateDirectories: true)
    Files.createDirectoryIfNeeded(at: storeURL.appending(directory: blobsDirectoryName))
}

After applying the fix, repeating step 2 of the reproduction produces a populated blobs/<sha1> file matching the entity row, and the response body renders correctly in PulseUI.

Scope

One-line change. Affects only the migration path — fresh installs already work because the initial Files.createDirectoryIfNeeded(at: blobsURL) in init is never invalidated.

removePreviousStore wipes storeURL on schema-version mismatch and
recreates only the top-level directory. The blobs subdirectory created
earlier in init() is gone for the remainder of the session, and every
Data.write(to: blobsURL/<hex>) inside storeBlob() silently fails because
the parent directory is missing. The metadata row is still saved, so the
console shows 'Unavailable / The response body was deleted from the
store to reduce its size' for any body larger than inlineLimit (16 KB
compressed) captured during the upgrade session.

Mirror the pattern already used by _removeAll() and recreate the blobs
directory at the end of removePreviousStore.
@kean kean merged commit 3efa481 into kean:main May 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants