Electron autoUpdater: Post-Mortem Update Failure Detection
Problem
When a Squirrel.Mac update fails after the app has already quit (e.g. due to
TCC permission denial on a non-/Applications volume), there is no way for
the app to know what went wrong on next launch. The user just finds their app
gone with no explanation.
Root cause in the log:
NSPOSIXErrorDomain Code=1 "Operation not permitted"
NSLocalizedDescription: %APPLICATION_NAME% couldn't be copied because
you don't have permission to access "Applications".
Why Squirrel Cannot Currently Report This
- ShipIt communicates with Squirrel via a one-way file-based channel
(
ShipItState.plist). There is no return channel. - ShipIt runs after the parent app has quit, so no Squirrel process is alive to receive errors at the time of failure.
- Squirrel's
relaunchToInstallUpdateerror signal only covers failures before ShipIt is launched — post-launch failures are invisible to it. - ShipIt writes its errors only to
ShipIt_stderr.login the app's ShipIt cache directory.
Proposed Solution
Add post-mortem detection: on next app launch, read the ShipIt stderr log, parse for known failure patterns, and surface them as a structured event that Electron app developers can consume.
Contribution Path
Squirrel/Squirrel.Mac → electron/electron → Apps (VS code, Claude, ...)
(detection) (event API) (UI / dialog)
Each step is an independent PR and can be pursued in parallel.
Step 1 — Squirrel.Mac
Repo: https://github.com/Squirrel/Squirrel.Mac
Add a method to SQRLUpdater.m that runs on app startup:
- Locate the ShipIt stderr log via
SQRLDirectoryManager(path is already known to Squirrel) - Parse the last
Installation error:entry - Detect known failure patterns:
NSPOSIXErrorDomain Code=1→ TCC permission denied (EPERM)SQRLCodeSignatureErrorDomain Code=-1→ code signature validation failedSQRLInstallerErrorDomain Code=-7→ cross-volume move failed
- Expose a new signal
lastInstallErroronSQRLUpdaterreturning a structuredNSErrorwith the reason
Key point: This is read-only on startup — no changes to install logic, low risk.
Step 2 — Electron
Repo: https://github.com/electron/electron
Apply a squirrel.patch to the Squirrel.Mac dependency (bypassing the
archived fork) and wire the new lastInstallError signal into Electron's
autoUpdater module as a new event:
autoUpdater.on('update-failed-last-session', (event, details) => {
// details.reason: 'permission-denied' | 'code-signature' | 'cross-volume'
// details.message: human-readable description
})
Squirrel.patch approach sidesteps the archived fork risk entirely — Electron applies the patch at build time against the upstream Squirrel.Mac source, no fork maintenance required.
Step 3 — Electron Apps
Listen for the new update-failed-last-session event and show a dialog:
autoUpdater.on('update-failed-last-session', (event, details) => {
if (details.reason === 'permission-denied') {
dialog.showMessageBox({
type: 'warning',
title: 'Update Failed',
message: 'The app could not update itself.',
detail:
'The updater (ShipIt) does not have permission to write to your ' +
'Applications folder. Please grant Full Disk Access to ShipIt in ' +
'System Settings to allow future updates.',
buttons: ['Open System Settings', 'Dismiss'],
}).then(result => {
if (result.response === 0) {
shell.openExternal(
'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles'
);
}
});
}
});
Why This Is Low Risk
- No changes to install logic — detection only
- ShipIt log path is deterministic, already managed by
SQRLDirectoryManager - Error patterns are specific and unlikely to produce false positives
squirrel.patchapproach avoids dependency on the archived Electron fork- Each PR is independently mergeable and testable
Failure Patterns to Detect
| Error | Domain | Code | Reason |
|---|---|---|---|
| TCC permission denied | NSPOSIXErrorDomain | 1 | ShipIt needs Full Disk Access |
| Code signature invalid | SQRLCodeSignatureErrorDomain | -1 | Downloaded update is corrupt or tampered |
| Cross-volume move failed | SQRLInstallerErrorDomain | -7 | App on external volume, copy failed |
References
- ShipIt log location:
~/Library/Caches/<bundle-id>.ShipIt/ShipIt_stderr.log - Full Disk Access System Settings URL:
x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles - Squirrel.Mac source:
https://github.com/Squirrel/Squirrel.Mac - Electron autoUpdater docs:
https://www.electronjs.org/docs/latest/api/auto-updater