fix(desktop): classify out-of-space update failures by free disk, not message text
Problem
macOS auto-update failures caused by a full user disk are captured as Sentry exceptions. They arrive several times a day and can never be archived: the failure message embeds the full per-user staging path, so every occurrence fingerprints as a brand-new issue.
Two further consequences:
- The staging path contains the user's macOS account name, so the account name lands in the title of a public-facing Sentry issue list. That alone is reason to stop capturing these, independent of the noise.
- Real update bugs are buried under a daily stream of issues that are not bugs.
Root cause
isEnvironmentUpdateError already existed to keep user-environment update failures out of Sentry, but it only recognised two shapes: a Node errno prefix (ENOSPC:, EACCES:, …) combined with an updater path marker, or one of two hardcoded English phrases.
Out-of-space failures match neither:
- The extraction step is a macOS command-line tool, not Node, so its message carries no errno prefix — just the tool name, the file it was writing, and a description.
- The download step surfaces a Cocoa
NSError, whose text is localised to the user's system language. Evidence so far includes English and Spanish; there is no reason to think that set is closed. The localised variant carries no path at all, only an archive filename.
So the guard never fired and the handler captured every one.
Fix
Classify on a structural signal instead of the message text: on an updater error, statfs the volume holding the updater staging caches and treat the failure as environmental when that volume cannot hold a staged update.
- Language-independent — it never reads the message for this case, so it covers every localisation, including ones we have not seen yet.
- Path-independent — it uses the staging directory we already own rather than parsing a path out of user-supplied text. This matters because the localised variant has no path to parse.
- Fails toward visibility — if the volume cannot be queried the reader returns
nulland the error reports as before.
Threshold is 1 GiB. A macOS update downloads a ~600MB archive into the user's cache directory and unpacks it alongside itself, so a volume under 1 GiB free cannot stage one regardless of what our updater does.
I looked for a stronger signal first and there is none. MacUpdater forwards Electron's native autoUpdater error verbatim (this.nativeUpdater.on("error", it => this.emit("error", it))); that error is built from an NSError localised description with no code, no errno, no exit status, and no electron-updater error class. Free space on the staging volume is the most structural signal reachable at this site. No message-regex matching was added.
The pure predicate moved to a sibling module so it can be tested without booting the main-process entry point; its existing text branches are unchanged.
What still reports as a Sentry exception
Everything that is not the named condition, whenever the staging volume has ≥1 GiB free:
- corrupt downloads (sha512 checksum mismatch)
- code-signature verification failures
- feed and manifest errors (404s, unparseable
latest-mac.yml) - extraction and staging failures for any reason other than space — including the same tool failing with a full disk's message on a volume that is not full
Unchanged from before this PR: read-only-volume failures, the request timed out, and errno-plus-updater-path failures remain classified as environmental.
The tradeoff, stated plainly: while the volume is under 1 GiB free, every updater error is treated as environmental, including a genuine bug that happens to coincide with a full disk. That is deliberate — no update can be staged on such a volume, so no failure on it is actionable for us — but it is broader than "out of space" alone and is pinned by a test so it cannot change silently.
What is unchanged
This is a classification change only. log.error and the AUTO_UPDATE_STATUS.ERROR status emitted to the renderer are untouched, so the app still tells the user the update failed with the same message. No beforeSend filter, no Sentry-side suppression — the decision stays at the capture site. Nothing about update staging, disk cleanup, or the user's disk-full condition is touched.
Verification
bunx biome check on the changed files:
Checked 3 files in 12ms. No fixes applied.
cd apps/desktop && bun run typecheck:
$ bun run generate:icons && bun run generate:routes
$ bun run scripts/generate-file-icons.ts
Generated file icons: 1067 SVGs copied, 2046 file names, 1152 extensions, 4528 folder names
$ tsr generate
$ tsc --noEmit
bun test apps/desktop/src/main/lib:
546 pass
0 fail
1332 expect() calls
Ran 546 tests across 30 files. [9.70s]
The new test pins both captured message shapes (account name and archive digest replaced with placeholders in the fixtures), that both report when the volume has room, that checksum/signature/feed failures still report, and the full-volume tradeoff above.
Refs DESKTOP-W8
Refs DESKTOP-W6
Refs DESKTOP-W5
Refs DESKTOP-W3
Refs DESKTOP-W2
Refs DESKTOP-W0
https://claude.ai/code/session_893897ca-3db6-4631-ba08-967e4ea0b7d5
Summary by cubic
Classifies macOS auto-update “disk full” failures by free space on the staging volume to stop Sentry issue churn and PII leakage. Previously localized, path-specific errors always captured as new issues; now volumes with <1 GiB free classify as environment, while known electron-updater release defects still report regardless of disk state.
- Centralizes classification in
update-error-classification.ts;auto-updater.tsaddsfreeStagingBytes()viastatfsSync(app.getPath("userData"))and passes it to the predicate. If the volume query fails, we fall back to capture. - Checks defect patterns first so they always report: checksum mismatch and no-checksum wording, code-signature failures, unreadable feed, “Cannot find channel”, and “No files provided”.
- Keeps prior environment matches (“read-only volume”, “the request timed out”, errno+updater-path markers). No Sentry-side filters; logs and renderer
AUTO_UPDATE_STATUS.ERRORare unchanged. - Tests cover English and Spanish disk-full messages and the expanded defect set; confirm defects report even when <1 GiB free, and unknown failures on a full volume still classify as environment.
Summary by CodeRabbit
- Bug Fixes
- Improved software update reliability when the device has insufficient storage or a read-only drive.
- Better distinguishes local device conditions from genuine update service failures.
- Improved handling of update timeouts and common filesystem errors.
- Recognizes storage-related update failures across English and Spanish error messages.
- Reduced unnecessary error reporting for failures caused by local storage conditions, helping diagnostics focus on actionable service issues.