The NAS had terabytes free. Foldic said it was full
August 14, 2026 · Foldic development notes
A user's backup stopped with "the destination is full." The destination was a NAS with terabytes free. Foldic refused to continue and let go of every album still waiting in the queue — nothing was deleted, nothing was written wrong, but the sync was over and the sentence on screen was false. The cause was mine, and it sat inside the very feature we had shipped days earlier to make disk-full failures clearer.
The feature that carried the bug
In 0.7.1 we shipped this: before it, a genuinely full destination was miserable. Every remaining file failed one by one, and by the time the reason travels through PhotoKit to the app it has often been stripped down to a hollow error code — so the wall of red never once said "the disk is full". Our fix: when a file fails to write, probe the volume. Read-only, or less than 20 MB free, stops the whole queue and says why in one plain sentence.
Stopping was right. The probe was wrong.
A number that means something else on half the world's disks
The probe asked the system for the volume's free space — specifically volumeAvailableCapacityForImportantUsage, the figure that also counts purgeable space. That figure is an APFS notion. Ask an exFAT volume — the format most USB drives arrive in — and it answers 0, not "I don't know". Measured, not read in documentation: a drive with 206 MB genuinely free reports 0 here. An SMB share — the protocol a NAS speaks — answers the same way.
My code had a fallback — but it was written as "if the first figure is missing, use the second". Zero is not missing. Zero is an answer, and a confidently wrong one. So on every exFAT drive and network share: free = 0, 0 is less than 20 MB, verdict: full. From that moment, any single failed write — one file, for any reason — escalated into "the destination is full" and stopped everything.
We had already learned this once
Here is the embarrassing part. The native bridge of Foldic's previous incarnation (the one 0.7.0 replaced) had a > 0 check sitting next to this very figure — it confirmed the number was positive before believing it. The Swift rewrite lost that one-line lesson. And the comment I wrote right above my own line shows I half-knew: "network volumes often report only the plain one, so ask for both." Asking for both does nothing when the first one confidently answers zero. A comment that half-knows is worse than no comment: it reads as if the problem were handled.
Stop inferring. The filesystem already said what happened
The fix does not tune the guess; it deletes it. The write that just failed carries the filesystem's own explanation: Cocoa error 640 — or POSIX ENOSPC underneath — means full. Cocoa 642, or EROFS, means read-only. Everything else stays exactly what it was: one file that failed, not a verdict about the whole volume.
Both codes were confirmed against real volumes rather than looked up: filling an exFAT disk image yields 640 on top of 28; writing to one mounted read-only yields 642 on top of 30. And because PhotoKit has been seen to crush a real cause down to a hollow −1 with the truth buried one level below, the wrapped error is inspected too.
No threshold. No per-filesystem special case. The whole class of bug is gone. The capacity figure that lied survives only where it always behaved — the free-space display in the status bar. That code never believed a zero in the first place: when the fancy figure answers 0 it shows the plain one, and when both do, it shows nothing. Nothing rather than zero, because a 0 on screen also says "your disk is full", and saying that falsely is worse than staying silent.
Who hit this
The bug shipped in 0.7.1 on both platforms and was still aboard in 0.7.2. And the blast radius is wider than the report that found it: not just NAS. Every exFAT USB drive answers 0 — and an exFAT USB drive is the single most common thing an iPhone backs up to. If Foldic told you the destination was full while the drive plainly was not, this was why. Nothing was harmed: the stop is a refusal, not a cleanup, and syncing again after the update continues from where it stopped.
Writing the rule down, this time
The verdict now lives in a file with no dependencies, testable without a photo library or a disk: seven tests, including that an ordinary permission failure must never be read as a statement about the volume. And the lesson, at the end where it belongs: when the filesystem is willing to tell you what happened, do not reconstruct what happened from a number — least of all a number that means "how much can APFS purge" on this disk and "0" on the next. A rewrite discards lessons like this silently; the only place they survive is a test that fails when they are forgotten.
When is it fixed? Fixed — 0.8 is out on both platforms. If a false "destination is full" stopped your queue, nothing was harmed: update, sync again, and it continues from where it stopped.
한 사용자의 백업이 "대상이 가득 찼습니다"로 멈췄습니다. 그 대상은 수 TB가 비어 있는 NAS였습니다. Foldic은 계속하기를 거부하고 큐에서 기다리던 앨범을 전부 놓아 버렸습니다 — 아무것도 지우지 않았고 아무것도 잘못 쓰지 않았지만, 동기화는 그렇게 끝났고 화면의 그 문장은 거짓이었습니다. 원인은 저였고, 그것도 "디스크 가득 참을 분명하게 알리자"며 며칠 전에 내놓은 바로 그 기능 안에 있었습니다.
버그를 실은 채 출시된 기능
0.7.1에서 우리는 이런 기능을 내놓았습니다. 그 전에는 정말로 가득 찬 대상이 참혹했습니다: 남은 파일이 하나씩 차례로 실패하고, 그 이유는 PhotoKit을 지나 앱에 도착할 즈음이면 속이 빈 오류 코드로 벗겨져 있기 일쑤였습니다 — 그래서 빨간 벽 어디에도 "디스크가 가득 참"이라고는 적혀 있지 않았습니다. 우리의 처방: 파일 쓰기가 실패하면 볼륨을 조사한다 — 읽기 전용이거나 남은 공간이 20 MB 미만이면, 큐 전체를 멈추고 평범한 한 문장으로 이유를 말한다.
멈추는 것은 옳았습니다. 조사하는 방법이 틀렸습니다.
세상의 절반의 디스크에서는 다른 뜻이 되는 숫자
프로브가 시스템에 물은 것은 볼륨의 남은 공간 — 정확히는 volumeAvailableCapacityForImportantUsage, 정리 가능한 공간까지 세는 그 숫자였습니다. 그것은 APFS의 개념입니다. exFAT 볼륨 — 대부분의 USB 드라이브가 공장에서 이 포맷으로 나옵니다 — 에 물으면 답은 "모른다"가 아니라 0입니다. 문서가 아니라 실측으로: 실제로 206 MB가 비어 있는 드라이브가 여기서는 0이라고 보고합니다. SMB 공유 — NAS가 쓰는 프로토콜 — 도 똑같이 답합니다.
제 코드에는 대비책이 있었습니다 — 다만 "첫 번째 숫자를 못 얻으면 두 번째를 쓴다"라고 적혀 있었습니다. 0은 '못 얻음'이 아닙니다. 0은 대답이고, 그것도 자신만만하게 틀린 대답입니다. 그리하여 모든 exFAT 드라이브와 네트워크 공유에서: 남음 = 0, 0은 20 MB 미만, 판정: 가득 참. 그 순간부터 단 한 번의 쓰기 실패가 — 파일 하나, 이유 불문 — "대상이 가득 참"으로 승격되어 모든 것을 멈췄습니다.
이 교훈은 이미 한 번 배웠던 것
민망한 대목은 여기입니다. Foldic의 전신(0.7.0이 교체한 그 앱)의 네이티브 브리지에는 바로 이 숫자 옆에 > 0 검사가 있었습니다 — 양수인지 확인한 뒤에야 믿었습니다. Swift로 다시 쓰면서 그 한 줄의 교훈이 사라졌습니다. 그리고 제가 그 줄 위에 직접 쓴 주석은 제가 반쯤은 알고 있었음을 보여 줍니다: "네트워크 볼륨은 흔히 평범한 쪽 숫자만 보고한다. 그러니 둘 다 물어라." 첫 번째가 자신 있게 0이라고 답하면, 둘 다 물어도 아무 소용이 없습니다. 반쯤 아는 주석은 없는 것보다 나쁩니다: 문제가 이미 처리된 것처럼 읽히니까요.
추측을 멈춘다. 파일 시스템이 이미 말했다
수정은 추측을 더 정교하게 만드는 것이 아니라 추측 자체를 지우는 것이었습니다. 방금 실패한 그 쓰기는 파일 시스템 자신의 설명을 지니고 있습니다: Cocoa 오류 640 — 또는 그 아래의 POSIX ENOSPC — 는 가득 참. Cocoa 642, 또는 EROFS는 읽기 전용. 그 밖의 모든 것은 원래대로: 파일 하나가 실패했다는 것일 뿐, 볼륨 전체에 대한 판결이 아닙니다.
두 코드 모두 표를 찾아본 것이 아니라 실제 볼륨으로 확인했습니다: exFAT 디스크 이미지를 가득 채우면 28 위에 640이, 읽기 전용으로 마운트한 볼륨에 쓰면 30 위에 642가 돌아옵니다. 그리고 PhotoKit이 진짜 원인을 속이 빈 −1로 뭉개고 진실을 한 층 아래에 묻는 것을 본 적이 있으므로, 감싸인 오류의 속도 함께 검사합니다.
임계값 없음. 파일 시스템별 특례 없음. 이 부류의 버그가 통째로 사라졌습니다. 거짓말을 했던 그 용량 숫자는 늘 얌전했던 자리에만 남아 있습니다 — 상태 표시줄의 남은 공간 표시. 그 코드는 처음부터 0을 믿지 않았습니다: 고급 숫자가 0이면 평범한 숫자를, 둘 다 0이면 아무것도 표시하지 않습니다. 0 대신 공백 — 화면의 0 역시 "디스크가 가득 찼다"고 말하는 것이고, 그것을 거짓으로 말하는 것은 침묵보다 나쁘기 때문입니다.
누가 겪었나
이 버그는 0.7.1에 실려 두 플랫폼 모두에 나갔고, 0.7.2에도 남아 있었습니다. 그리고 영향 범위는 발견해 준 제보보다 넓습니다: NAS만이 아닙니다. 모든 exFAT USB 드라이브가 0이라고 답합니다 — 그리고 exFAT USB 드라이브야말로 iPhone 백업의 가장 흔한 대상입니다. 드라이브가 분명히 비어 있는데 Foldic이 가득 찼다고 했다면, 원인은 이것이었습니다. 아무것도 다치지 않았습니다: 그 멈춤은 거부이지 정리가 아니며, 업데이트 후 다시 동기화하면 멈춘 곳에서 이어집니다.
이번에는 규칙을 적어 둔다
판정은 이제 의존성 없는 파일에 삽니다. 사진 보관함도 디스크도 없이 테스트할 수 있습니다: 일곱 개의 테스트, 그중에는 "평범한 권한 실패를 볼륨에 대한 판결로 읽어서는 안 된다"도 있습니다. 그리고 교훈은, 있어야 할 자리인 끝에: 파일 시스템이 무슨 일이 있었는지 말해 줄 때, 다른 숫자로 무슨 일이 있었는지를 재구성하지 말 것 — 특히 이 디스크에서는 "APFS가 얼마나 정리할 수 있는가"를 뜻하고 옆 디스크에서는 "0"을 뜻하는 숫자로는. 다시 쓰기는 이런 교훈을 소리 없이 버립니다. 교훈이 살아남는 유일한 곳은, 잊혔을 때 실패하는 테스트입니다.
언제 고쳐지나요? 고쳐졌습니다 — 0.8이 두 플랫폼 모두에 출시되었습니다. 가짜 "대상이 가득 참"이 큐를 멈췄더라도 아무것도 다치지 않았습니다: 업데이트하고 다시 동기화하면 멈춘 곳에서 계속됩니다.