src/skills/desktop-updates-electron-updater/SKILL.md
Cross-platform auto-update patterns with electron-updater (electron-builder ecosystem)
npx skillsauth add agents-inc/skills desktop-updates-electron-updaterInstall 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.
Quick Guide: Use
electron-updater(from electron-builder) for cross-platform auto-updates. It supports macOS (DMG), Windows (NSIS), and Linux (AppImage/DEB/RPM). Configure a provider (GitHub, S3, generic server) in yourelectron-builderconfig. The updater emits lifecycle events:checking-for-update->update-available->download-progress->update-downloaded. SetautoDownload: falsefor manual download control. Use channels (latest/beta/alpha) for staged releases andstagingPercentagefor gradual rollouts. Code signing is mandatory on macOS and strongly recommended on Windows.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST guard update checks with app.isPackaged -- calling checkForUpdates() in development causes confusing errors and network calls to non-existent endpoints)
(You MUST handle the error event on the updater -- unhandled update errors crash the main process)
(You MUST code-sign macOS builds -- unsigned apps cannot auto-update and the updater silently fails)
(You MUST NOT call quitAndInstall() without confirming the user's intent -- forcing a restart mid-work causes data loss)
(You MUST use named constants for all intervals and timeouts -- no magic numbers in setInterval or retry logic)
</critical_requirements>
Auto-detection: electron-updater, autoUpdater from electron-updater, checkForUpdates, checkForUpdatesAndNotify, update-available, update-downloaded, download-progress, quitAndInstall, autoDownload, stagingPercentage, dev-app-update.yml, NsisUpdater, MacUpdater, AppImageUpdater, setFeedURL, allowPrerelease, allowDowngrade, forceDevUpdateConfig, disableDifferentialDownload
<philosophy>When to use:
When NOT to use:
autoUpdater module instead)Import autoUpdater from electron-updater (not Electron's built-in module). Wire up lifecycle events in the main process after the app is ready.
import { autoUpdater } from "electron-updater";
const CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
function setupAutoUpdater(mainWindow) {
if (!app.isPackaged) return; // Never check in development
autoUpdater.on("update-available", (info) => {
mainWindow.webContents.send("update-available", info);
});
autoUpdater.on("update-downloaded", (info) => {
mainWindow.webContents.send("update-downloaded", info);
});
autoUpdater.on("error", (error) => {
log.error("Update error:", error);
});
autoUpdater.checkForUpdatesAndNotify();
setInterval(() => autoUpdater.checkForUpdates(), CHECK_INTERVAL_MS);
}
Key point: checkForUpdatesAndNotify() checks and shows a native OS notification when an update downloads. Use checkForUpdates() for silent checks when you handle UI yourself. See examples/core.md.
Set autoDownload: false to let users decide when to download. This is essential for metered connections or large updates.
autoUpdater.autoDownload = false;
autoUpdater.on("update-available", (info) => {
// Show UI prompt -- user decides whether to download
mainWindow.webContents.send("update-available", info);
});
// User clicks "Download" in the renderer
ipcMain.handle("start-update-download", () => {
return autoUpdater.downloadUpdate();
});
Key point: With autoDownload: false, the download-progress and update-downloaded events only fire after you explicitly call downloadUpdate(). See examples/core.md.
Configure where the updater looks for releases. The provider is set in your electron-builder config file and can be overridden at runtime with setFeedURL().
# electron-builder.yml -- GitHub provider (default if GH_TOKEN set)
publish:
provider: github
owner: my-org
repo: my-app
# electron-builder.yml -- Generic HTTP server
publish:
provider: generic
url: https://releases.example.com/updates
# electron-builder.yml -- S3 bucket
publish:
provider: s3
bucket: my-app-releases
region: us-east-1
path: /releases
Key point: The first provider in the list is the auto-update source. Additional providers are publishing targets only. See examples/core.md for runtime setFeedURL() override.
Channels distribute pre-release versions to specific user groups. Append -beta or -alpha to your package.json version to produce channel-specific metadata files.
{ "version": "2.1.0-beta" }
# electron-builder.yml
generateUpdatesFilesForAllChannels: true
// Switch channel at runtime
autoUpdater.channel = "beta";
// Setting channel automatically enables allowDowngrade
Key point: Users on alpha receive alpha, beta, and stable releases. Users on beta receive beta and stable. Users on latest (stable) only receive stable releases. See examples/channels-and-rollouts.md.
Roll out updates gradually by setting stagingPercentage in your metadata YAML file. The updater assigns each installation a persistent random ID and compares it against the percentage.
# latest.yml (manually edited after publishing)
version: 2.1.0
stagingPercentage: 10 # Ship to 10% of users first
Key point: Increment the version when pulling a broken staged release -- users already on the broken version will not downgrade to the same version number. See examples/channels-and-rollouts.md.
Network failures during update checks are common. Wrap retry logic around the check and always handle the error event.
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 30_000; // 30 seconds
autoUpdater.on("error", (error) => {
log.error("Auto-update error:", error.message);
// Notify renderer for user-facing feedback
mainWindow.webContents.send("update-error", error.message);
});
Key point: The error event fires for network failures, signature verification failures, and corrupted downloads. Never ignore it -- unhandled errors in the updater crash the main process. See examples/core.md for retry with exponential backoff.
Use dev-app-update.yml and forceDevUpdateConfig to test the update flow without packaging.
# dev-app-update.yml (project root)
provider: generic
url: http://localhost:8080/updates
if (!app.isPackaged) {
autoUpdater.forceDevUpdateConfig = true;
}
Key point: You still need a local HTTP server serving the update artifacts (installer + latest.yml). Minio is commonly used as a local S3-compatible server for this purpose. See examples/testing.md.
<decision_framework>
Building with electron-builder?
+-- YES --> Use electron-updater (this skill)
+-- NO --> Building with Electron Forge?
+-- YES --> Using Squirrel maker?
| +-- YES --> Use Electron's built-in autoUpdater module
| +-- NO --> Can use electron-updater with custom config
+-- NO --> Distributing via app store?
+-- YES --> Use the store's native update mechanism
+-- NO --> Use electron-updater with generic provider
Where are your releases hosted?
+-- GitHub Releases (public or private repo)
| +-- Use provider: github
+-- AWS S3 or compatible (MinIO, Backblaze B2)
| +-- Use provider: s3
+-- DigitalOcean Spaces
| +-- Use provider: spaces
+-- Any HTTP(S) server (Nginx, CDN, custom)
| +-- Use provider: generic
+-- Keygen (license-gated updates)
+-- Use provider: keygen
Should updates download automatically?
+-- App is small (<50 MB) and users expect seamless updates?
| +-- autoDownload: true (default) + checkForUpdatesAndNotify()
+-- App is large or users are on metered connections?
| +-- autoDownload: false + show download prompt in UI
+-- Enterprise environment with IT-managed rollouts?
+-- autoDownload: false + admin-controlled trigger
</decision_framework>
Detailed resources:
<red_flags>
Critical Issues:
checkForUpdates() or checkForUpdatesAndNotify() outside app.isPackaged guard -- causes errors and unnecessary network calls in developmenterror event on autoUpdater -- unhandled update errors crash the main processquitAndInstall() immediately without user confirmation -- forces restart, risks data lossautoUpdater module instead of importing from electron-updater -- different API, different behavior, no Linux supportArchitecture Issues:
electron-updater must run in the main process onlyautoInstallOnAppQuit when autoDownload is true -- users never get the update if they don't explicitly restart.nupkg delta files, NSIS uses blockmap-based differential downloads)Staged Rollout Mistakes:
stagingPercentage: 0 expecting it to block all updates -- behavior is undefined at 0; use channels for access control insteadstagingPercentage in latest.yml without re-signing -- signature validation failsCommon Mistakes:
generateUpdatesFilesForAllChannels: true when using beta/alpha channels -- only the current channel's YAML is generatedallowPrerelease: true on the client instead of proper channels -- allowPrerelease only works with GitHub provider and is less predictable than channelsautoUpdater.logger during debugging -- update failures are silent without logging configuredelectron-builder publish config -- the build process auto-generates correct metadata only when publish is configuredGotchas & Edge Cases:
checkForUpdatesAndNotify() returns null when app.isPackaged is false -- it silently skips in devquitAndInstall(true) (silent mode) only works on Windows NSIS -- macOS ignores the isSilent parameterdownload-progress event does not fire when differential download is used -- only fires for full downloadsverifyUpdateCodeSignature) -- unsigned updates are rejectedsetFeedURL() overrides the provider from electron-builder config at runtime -- useful for switching environments but can cause confusion if called unintentionally</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST guard update checks with app.isPackaged -- calling checkForUpdates() in development causes confusing errors and network calls to non-existent endpoints)
(You MUST handle the error event on the updater -- unhandled update errors crash the main process)
(You MUST code-sign macOS builds -- unsigned apps cannot auto-update and the updater silently fails)
(You MUST NOT call quitAndInstall() without confirming the user's intent -- forcing a restart mid-work causes data loss)
(You MUST use named constants for all intervals and timeouts -- no magic numbers in setInterval or retry logic)
Failure to follow these rules will cause silent update failures, crashes, or data loss for end users.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events