ai/ios-skills/ios-axiom-storage-diag/SKILL.md
Use when debugging 'files disappeared', 'data missing after restart', 'backup too large', 'can't save file', 'file not found', 'storage full error', 'file inaccessible when locked' - systematic local file storage diagnostics
npx skillsauth add kurko/dotfiles axiom-storage-diagInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Core principle 90% of file storage problems stem from choosing the wrong storage location, misunderstanding file protection levels, or missing backup exclusions—not iOS file system bugs.
The iOS file system is battle-tested across millions of apps and devices. If your files are disappearing, becoming inaccessible, or causing backup issues, the problem is almost always in storage location choice or protection configuration.
If you see ANY of these:
❌ FORBIDDEN "iOS deleted my files, the file system is broken"
ALWAYS check these FIRST (before changing code):
// 1. Check WHERE file is stored
func diagnoseFileLocation(_ url: URL) {
let path = url.path
if path.contains("/tmp/") {
print("⚠️ File in tmp/ - system purges aggressively")
} else if path.contains("/Caches/") {
print("⚠️ File in Caches/ - purged under storage pressure")
} else if path.contains("/Documents/") {
print("✅ File in Documents/ - never purged, backed up")
} else if path.contains("/Library/Application Support/") {
print("✅ File in Application Support/ - never purged, backed up")
}
}
// 2. Check file protection level
func diagnoseFileProtection(_ url: URL) throws {
let attrs = try FileManager.default.attributesOfItem(atPath: url.path)
if let protection = attrs[.protectionKey] as? FileProtectionType {
print("Protection: \(protection)")
if protection == .complete {
print("⚠️ File inaccessible when device locked")
}
}
}
// 3. Check backup status
func diagnoseBackupStatus(_ url: URL) throws {
let values = try url.resourceValues(forKeys: [.isExcludedFromBackupKey])
if let excluded = values.isExcludedFromBackup {
print("Excluded from backup: \(excluded)")
}
}
// 4. Check file existence and size
func diagnoseFileState(_ url: URL) {
if FileManager.default.fileExists(atPath: url.path) {
if let size = try? FileManager.default.attributesOfItem(atPath: url.path)[.size] as? Int64 {
print("File exists, size: \(size) bytes")
}
} else {
print("❌ File does not exist")
}
}
Files missing? → Check where stored
├─ Disappeared after device restart
│ ├─ Was in tmp/? → EXPECTED (tmp/ purged on reboot)
│ │ → FIX: Move to Caches/ or Application Support/
│ │
│ ├─ Was in Caches/? → System purged (storage pressure)
│ │ → FIX: Move to Application Support/ if can't be regenerated
│ │
│ └─ Protection level .complete? → Inaccessible until unlock
│ → FIX: Wait for unlock or use .completeUntilFirstUserAuthentication
│
├─ Disappeared randomly (weeks later)
│ ├─ In Caches/? → System purged under storage pressure
│ │ → EXPECTED if re-downloadable
│ │ → FIX: Re-download when needed, or move to Application Support/
│ │
│ └─ In Documents or Application Support/?
│ → Check if user deleted app (purges all data)
│ → Check iOS update (rare, but check migration path)
│
└─ Only some files missing
→ Check isExcludedFromBackup + iCloud sync
→ Check if file names have special characters
→ Check file permissions
Can't access file?
├─ Error: "No permission" or NSFileReadNoPermissionError
│ ├─ Device locked? → Check file protection
│ │ └─ .complete protection? → Wait for unlock
│ │ → FIX: Use .completeUntilFirstUserAuthentication
│ │
│ └─ Background task accessing? → .complete blocks background
│ → FIX: Change to .completeUntilFirstUserAuthentication
│
├─ File exists but read returns empty/nil
│ └─ Check actual file size on disk
│ → May be zero-byte file from failed write
│
└─ File exists in debugger but not at runtime
→ Check if using wrong directory (Documents vs Caches)
→ Check URL construction
App backup > 500 MB?
├─ Check Documents directory size
│ └─ Large files (>10 MB each)?
│ ├─ Can they be re-downloaded? → Move to Caches + isExcludedFromBackup
│ └─ User-created? → Keep in Documents (warn user if >1 GB)
│
├─ Check Application Support size
│ └─ Downloaded media/podcasts?
│ → Mark isExcludedFromBackup = true
│
└─ Audit backup with code:
```swift
func auditBackupSize() {
let docsURL = FileManager.default.urls(
for: .documentDirectory,
in: .userDomainMask
)[0]
let size = getDirectorySize(url: docsURL)
print("Documents (backed up): \(size / 1_000_000) MB")
}
```
Symptom: Temp files missing after restart or even during app lifecycle
Cause: tmp/ is purged aggressively by system
Fix:
// ❌ WRONG: Using tmp/ for anything that should persist
let tmpURL = FileManager.default.temporaryDirectory
let fileURL = tmpURL.appendingPathComponent("data.json")
try data.write(to: fileURL) // WILL BE DELETED
// ✅ CORRECT: Use Caches/ for re-generable data
let cacheURL = FileManager.default.urls(
for: .cachesDirectory,
in: .userDomainMask
)[0]
let fileURL = cacheURL.appendingPathComponent("data.json")
try data.write(to: fileURL)
Symptom: Downloaded content disappears weeks later
Cause: Caches/ is purged under storage pressure (expected behavior)
Fix: Either re-download on demand OR move to Application Support if can't be regenerated
// ✅ CORRECT: Handle missing cache gracefully
func loadCachedImage(url: URL) async throws -> UIImage {
let cacheURL = getCacheURL(for: url)
// Try cache first
if FileManager.default.fileExists(atPath: cacheURL.path),
let data = try? Data(contentsOf: cacheURL),
let image = UIImage(data: data) {
return image
}
// Cache miss - re-download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: cacheURL)
return UIImage(data: data)!
}
Symptom: Background tasks fail with "permission denied"
Cause: Files with .complete protection inaccessible when locked
Fix:
// ❌ WRONG: .complete protection for background-accessed files
try data.write(to: url, options: .completeFileProtection)
// Background task fails when device locked
// ✅ CORRECT: Use .completeUntilFirstUserAuthentication
try data.write(
to: url,
options: .completeFileProtectionUntilFirstUserAuthentication
)
// Accessible in background after first unlock
Symptom: App backup >1 GB, app rejected or users complain
Cause: Downloaded content in Documents/ or not marked excluded
Fix:
// ✅ CORRECT: Exclude re-downloadable content
func downloadPodcast(url: URL) async throws {
let appSupportURL = FileManager.default.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
)[0]
let podcastURL = appSupportURL
.appendingPathComponent("Podcasts")
.appendingPathComponent(url.lastPathComponent)
// Download
let (data, _) = try await URLSession.shared.data(from: url)
try data.write(to: podcastURL)
// Mark excluded from backup
var resourceValues = URLResourceValues()
resourceValues.isExcludedFromBackup = true
try podcastURL.setResourceValues(resourceValues)
}
SYMPTOM: Users report lost photos after iOS update
DIAGNOSIS STEPS:
Check storage location (5 min):
// Were photos in Caches/?
let photosInCaches = path.contains("/Caches/")
// If yes → system purged them (expected)
Check if backed up (5 min):
// Check if excluded from backup
let excluded = try? url.resourceValues(
forKeys: [.isExcludedFromBackupKey]
).isExcludedFromBackup
// If excluded=true AND not synced → lost
Check migration path (10 min):
ROOT CAUSES (90% of cases):
FIX:
Run this on any storage problem:
func diagnoseStorageIssue(fileURL: URL) {
print("=== Storage Diagnosis ===")
// 1. Location
diagnoseFileLocation(fileURL)
// 2. Protection
try? diagnoseFileProtection(fileURL)
// 3. Backup status
try? diagnoseBackupStatus(fileURL)
// 4. File state
diagnoseFileState(fileURL)
// 5. Directory size
if let parentURL = fileURL.deletingLastPathComponent() as URL? {
let size = getDirectorySize(url: parentURL)
print("Parent directory size: \(size / 1_000_000) MB")
}
print("=== End Diagnosis ===")
}
axiom-storage — Correct storage location decisionsaxiom-file-protection-ref — Understanding protection levelsaxiom-storage-management-ref — Purge behavior and capacity APIsLast Updated: 2025-12-12 Skill Type: Diagnostic
data-ai
Merge the current worktree branch into main and sync main back. Use when the user says "merge to main", "ship it", "merge and continue", or after completing a task in a worktree and wanting to continue with the next one.
tools
Synchronize AI agent skills, commands, configs, permissions, hooks, and instructions across Claude Code, Codex CLI, and other Agent Skills-compatible tools. Use when the user asks to pull skills from Claude into Codex, sync Codex work back to Claude, migrate agent commands, reconcile frontmatter, update permissions, or keep agent setup files in parity.
testing
Write or update UI-independent use cases for QA. Use when the user says "write use cases", "add use cases", "QA use cases", "update use cases", "compose use cases", or when starting implementation of a new feature (after plan approval). Also activates for "what should we test", "regression cases", or "use cases for QA".
documentation
Skill on how to write a task. Use when user asks you to write a task (for Asana, Linear, Jira, Notion and equivalent). Also activates when user says "create task", "write task", or similar task creation workflow requests.