Desktop Launcher Review
Purpose
Review local desktop launchers, shortcuts, shell scripts, app wrappers, logging, and update safety for packaged desktop apps (Electron, Electron-Vite, Tauri, and similar). The core obligation is fail-closed: if the packaged build is missing or stale, the launcher must refuse to open an older app and report the failing command — never silently fall back to a previous build. The output names the launch mode, a freshness verdict, the fail-closed assessment, script issues with fixes, and the next safe command.
When to use
- A desktop launcher or shortcut script needs review before being handed to a non-technical user who will double-click it.
- The launcher is suspected to be pointing at a dev or hot-reload mode instead of the packaged production app.
- A build update was deployed but the launcher may still reference a stale
dist/ or out/ path — freshness must be verified.
- Atomic pointer-swap or rollback safety for launcher updates has not been designed or is broken.
When not to use
- The task is unrelated to mobile and desktop work.
- The work would require production deploys, destructive data actions, or secret disclosure.
- The concern is renderer or runtime security rather than launch correctness — use the Electron security review for
contextIsolation, CSP, and IPC hardening.
- A narrower skill or existing project instruction already covers the need.
Procedure
- Identify the launcher target. Determine whether the shortcut or script starts the packaged app or a dev/hot-reload mode (
electron ., electron-vite dev, npm run dev). The default expectation is the packaged app via the main launcher unless the user explicitly asked for dev mode.
- Check build freshness. Compare the packaged output against source, lockfile, assets, env shape, and any
.launcher/build-manifest.json or dist-app/current pointer. If any source input is newer than the packaged build, treat it as stale and require a rebuild before launch.
- Inspect the build and package pipeline and entrypoints. Read the build/package scripts, the Electron
main/preload/renderer entries, the output folder, and the launcher's actual target path. Confirm the entry paths declared in package.json or builder config exist inside the package.
- Apply fail-closed. If the packaged app is missing or stale, run the repo's production build/package command first. If that build fails, do not open an older packaged app — report the failing command and the log path and stop.
- Review launcher script safety. Check strict mode, quoting, absolute versus relative paths, error handling, the log destination, and graceful shutdown of the spawned process.
- Review update safety. Confirm a new build is staged and then the pointer is swapped atomically, the previous build is retained for rollback, and a manifest records what is current.
Concrete checks
Target and freshness:
- The launcher points at the
dist/, out/, or release/ packaged app, not electron . dev mode unless dev was requested.
- Source, asset, and lockfile mtimes are not newer than the packaged build.
.launcher/build-manifest.json exists and matches the current source; dist-app/current points at the fresh build.
Entrypoints and script hygiene:
- The
main and preload paths in package.json or builder config exist inside the package.
- Bash launchers use
set -euo pipefail; every path is quoted.
- No hardcoded per-user home paths that break on another machine.
Logging, shutdown, update:
- The launcher writes to a stable, timestamped log path per project policy.
- stdout and stderr are captured, not discarded to
/dev/null.
- Closing the launcher or the app cleans up child processes; no orphaned main process.
- Updates stage then swap the pointer atomically, retain the previous build, and update the manifest.
- A failed update leaves the previous working build intact and launchable.
Build correctness:
- The production build/package command is identified and runnable.
- The build output matches the launcher's expected target path.
- If the build fails, the launcher does not open any older app.
Commands
# --- launch mode ---
# is the launcher targeting packaged vs dev?
rg -n 'electron \.|electron-vite dev|npm run dev|\.app|dist|out|release' <launcher-script>
# --- build pipeline / entrypoints ---
# build pipeline + entry definitions
cat package.json | jq '{main, scripts, build}'
# entrypoint files actually exist inside the package?
rg -n '"main"|"preload"' package.json
# --- freshness ---
# any source newer than the packaged build pointer?
find src electron -newer dist-app/current -type f 2>/dev/null | head
# manifest present and what it records
cat .launcher/build-manifest.json 2>/dev/null | jq '.' 2>/dev/null
# --- script safety ---
# machine-specific paths, destructive ops, strict mode
rg -n '/Users/[a-z]+/|/home/[a-z]+/|rm -rf|set -e|set -euo pipefail' <launcher-script>
# log destination configured?
rg -n 'logfile|>>|tee|LOG_DIR|log_path' <launcher-script>
# --- shutdown / orphans ---
# child-process spawn and cleanup handling
rg -n 'spawn|exec|trap|kill|SIGTERM|on\(.close.' <launcher-script>
# --- renderer security (quick sanity, not a full audit) ---
# BrowserWindow webPreferences hardening
rg -n 'contextIsolation|nodeIntegration|webSecurity|sandbox' . | head
# --- update mechanism ---
# auto-update / pointer-swap logic
rg -n 'autoUpdater|checkForUpdates|symlink|rename\(|pointer' . | head
# --- code signing / notarization markers ---
# packaged-app signing config (presence, not contents)
rg -n 'codeSign|notarize|hardenedRuntime|entitlements' . | head
Common issues & anti-patterns
- Silent dev fallback: the launcher tries the packaged app, fails, and quietly runs
npm run dev instead — the user thinks they are testing the release build but are not.
- Stale-build open: the pointer still references last week's
dist-app/, so source changes never reach the user even though the launcher "works".
- Hardcoded home path: the script embeds a specific user home path, so it breaks the moment it runs on a different machine or account.
- No strict mode: a bash launcher without
set -euo pipefail keeps going after a failed build step and opens a half-baked app.
- Non-atomic update: the updater overwrites the live
dist-app/ in place; a mid-write crash leaves a corrupt, unlaunchable app with no rollback.
- Orphaned process: closing the launcher window leaves the Electron main process running, so the next launch spawns a duplicate.
- Logs to /dev/null: the launcher discards stdout and stderr, so when launch fails there is no evidence to diagnose.
- Unquoted path with spaces: an unquoted
$APP_DIR that contains a space splits into multiple arguments and the launch silently targets the wrong path.
- Hot-reload masquerading as production: the shortcut runs
electron-vite dev, so the user is unknowingly testing an unoptimized dev build with source maps and debug tooling.
- No build-freshness gate: the launcher opens whatever is in
dist-app/ with no check that it reflects current source, so fixes appear to "not work" because the old build still launches.
- Manifest not updated on swap: the pointer is swapped but
build-manifest.json is left stale, so the freshness check and rollback logic read the wrong current version.
- Renderer left unhardened: the launched window runs with
nodeIntegration: true and contextIsolation: false, so any renderer flaw becomes full local code execution.
- No previous build retained: the updater deletes the old build before staging the new one, so a failed update leaves nothing to roll back to.
Verification steps
Before clearing a launcher for handoff, walk it through these explicit states and confirm the behavior:
- Fresh build present: the launcher opens the packaged app and writes a timestamped log entry.
- Stale build: with source newer than the build pointer, the launcher refuses to open and prompts a rebuild.
- Missing build: with no packaged output, the launcher reports the build command and does not fall back to dev.
- Failed build: with the build command erroring, the launcher reports the failing command and log path and opens nothing.
- Update rollback: after a simulated failed update, the previous build is still intact and launchable.
- Clean shutdown: closing the app or launcher leaves no orphaned main process.
Required output
Return:
- Launch mode: packaged versus dev, and whether it matches intent.
- Build-freshness verdict: fresh, stale, or missing, with the evidence used.
- Fail-closed assessment: would a stale or failed build still open an old app?
- Launcher script issues:
file:line | issue | fix.
- Logging and update-safety findings.
- Next safe command: usually the production build or package.
Safety
- Never recommend opening an older packaged app when the current build is missing or its build failed — fail closed and report the failing command plus the log path.
- Do not run a destructive or long package build silently; recommend it and let the owner trigger it.
- No hardcoded machine-specific home paths — keep launchers portable across machines.
- Redact any secrets surfaced in launcher env or logs.
- Do not delete or overwrite an existing packaged build during review.
Completion criteria
Done means the launch mode is confirmed against intent; build freshness is judged from evidence; the fail-closed rule is verified (a stale or failed build cannot open an old app); launcher-script and update-safety issues are listed with file:line fixes; and the safe next command (build or package) is named.
1---2name: desktop-launcher-review3description: Use when you need to review local desktop launchers, shortcuts, shell scripts, app wrappers, logging, and update safety.4---56# Desktop Launcher Review78## Purpose910Review local desktop launchers, shortcuts, shell scripts, app wrappers, logging, and update safety for packaged desktop apps (Electron, Electron-Vite, Tauri, and similar). The core obligation is fail-closed: if the packaged build is missing or stale, the launcher must refuse to open an older app and report the failing command — never silently fall back to a previous build. The output names the launch mode, a freshness verdict, the fail-closed assessment, script issues with fixes, and the next safe command.1112## When to use1314- A desktop launcher or shortcut script needs review before being handed to a non-technical user who will double-click it.15- The launcher is suspected to be pointing at a dev or hot-reload mode instead of the packaged production app.16- A build update was deployed but the launcher may still reference a stale `dist/` or `out/` path — freshness must be verified.17- Atomic pointer-swap or rollback safety for launcher updates has not been designed or is broken.1819## When not to use2021- The task is unrelated to mobile and desktop work.22- The work would require production deploys, destructive data actions, or secret disclosure.23- The concern is renderer or runtime security rather than launch correctness — use the Electron security review for `contextIsolation`, CSP, and IPC hardening.24- A narrower skill or existing project instruction already covers the need.2526## Procedure27281. **Identify the launcher target.** Determine whether the shortcut or script starts the packaged app or a dev/hot-reload mode (`electron .`, `electron-vite dev`, `npm run dev`). The default expectation is the packaged app via the main launcher unless the user explicitly asked for dev mode.292. **Check build freshness.** Compare the packaged output against source, lockfile, assets, env shape, and any `.launcher/build-manifest.json` or `dist-app/current` pointer. If any source input is newer than the packaged build, treat it as stale and require a rebuild before launch.303. **Inspect the build and package pipeline and entrypoints.** Read the build/package scripts, the Electron `main`/`preload`/`renderer` entries, the output folder, and the launcher's actual target path. Confirm the entry paths declared in `package.json` or builder config exist inside the package.314. **Apply fail-closed.** If the packaged app is missing or stale, run the repo's production build/package command first. If that build fails, do not open an older packaged app — report the failing command and the log path and stop.325. **Review launcher script safety.** Check strict mode, quoting, absolute versus relative paths, error handling, the log destination, and graceful shutdown of the spawned process.336. **Review update safety.** Confirm a new build is staged and then the pointer is swapped atomically, the previous build is retained for rollback, and a manifest records what is current.3435## Concrete checks3637Target and freshness:38- The launcher points at the `dist/`, `out/`, or `release/` packaged app, not `electron .` dev mode unless dev was requested.39- Source, asset, and lockfile mtimes are not newer than the packaged build.40- `.launcher/build-manifest.json` exists and matches the current source; `dist-app/current` points at the fresh build.4142Entrypoints and script hygiene:43- The `main` and `preload` paths in `package.json` or builder config exist inside the package.44- Bash launchers use `set -euo pipefail`; every path is quoted.45- No hardcoded per-user home paths that break on another machine.4647Logging, shutdown, update:48- The launcher writes to a stable, timestamped log path per project policy.49- stdout and stderr are captured, not discarded to `/dev/null`.50- Closing the launcher or the app cleans up child processes; no orphaned main process.51- Updates stage then swap the pointer atomically, retain the previous build, and update the manifest.52- A failed update leaves the previous working build intact and launchable.5354Build correctness:55- The production build/package command is identified and runnable.56- The build output matches the launcher's expected target path.57- If the build fails, the launcher does not open any older app.5859## Commands6061```bash62# --- launch mode ---63# is the launcher targeting packaged vs dev?64rg -n 'electron \.|electron-vite dev|npm run dev|\.app|dist|out|release' <launcher-script>6566# --- build pipeline / entrypoints ---67# build pipeline + entry definitions68cat package.json | jq '{main, scripts, build}'6970# entrypoint files actually exist inside the package?71rg -n '"main"|"preload"' package.json7273# --- freshness ---74# any source newer than the packaged build pointer?75find src electron -newer dist-app/current -type f 2>/dev/null | head7677# manifest present and what it records78cat .launcher/build-manifest.json 2>/dev/null | jq '.' 2>/dev/null7980# --- script safety ---81# machine-specific paths, destructive ops, strict mode82rg -n '/Users/[a-z]+/|/home/[a-z]+/|rm -rf|set -e|set -euo pipefail' <launcher-script>8384# log destination configured?85rg -n 'logfile|>>|tee|LOG_DIR|log_path' <launcher-script>8687# --- shutdown / orphans ---88# child-process spawn and cleanup handling89rg -n 'spawn|exec|trap|kill|SIGTERM|on\(.close.' <launcher-script>9091# --- renderer security (quick sanity, not a full audit) ---92# BrowserWindow webPreferences hardening93rg -n 'contextIsolation|nodeIntegration|webSecurity|sandbox' . | head9495# --- update mechanism ---96# auto-update / pointer-swap logic97rg -n 'autoUpdater|checkForUpdates|symlink|rename\(|pointer' . | head9899# --- code signing / notarization markers ---100# packaged-app signing config (presence, not contents)101rg -n 'codeSign|notarize|hardenedRuntime|entitlements' . | head102```103104## Common issues & anti-patterns105106- **Silent dev fallback:** the launcher tries the packaged app, fails, and quietly runs `npm run dev` instead — the user thinks they are testing the release build but are not.107- **Stale-build open:** the pointer still references last week's `dist-app/`, so source changes never reach the user even though the launcher "works".108- **Hardcoded home path:** the script embeds a specific user home path, so it breaks the moment it runs on a different machine or account.109- **No strict mode:** a bash launcher without `set -euo pipefail` keeps going after a failed build step and opens a half-baked app.110- **Non-atomic update:** the updater overwrites the live `dist-app/` in place; a mid-write crash leaves a corrupt, unlaunchable app with no rollback.111- **Orphaned process:** closing the launcher window leaves the Electron main process running, so the next launch spawns a duplicate.112- **Logs to /dev/null:** the launcher discards stdout and stderr, so when launch fails there is no evidence to diagnose.113- **Unquoted path with spaces:** an unquoted `$APP_DIR` that contains a space splits into multiple arguments and the launch silently targets the wrong path.114- **Hot-reload masquerading as production:** the shortcut runs `electron-vite dev`, so the user is unknowingly testing an unoptimized dev build with source maps and debug tooling.115- **No build-freshness gate:** the launcher opens whatever is in `dist-app/` with no check that it reflects current source, so fixes appear to "not work" because the old build still launches.116- **Manifest not updated on swap:** the pointer is swapped but `build-manifest.json` is left stale, so the freshness check and rollback logic read the wrong current version.117- **Renderer left unhardened:** the launched window runs with `nodeIntegration: true` and `contextIsolation: false`, so any renderer flaw becomes full local code execution.118- **No previous build retained:** the updater deletes the old build before staging the new one, so a failed update leaves nothing to roll back to.119120## Verification steps121122Before clearing a launcher for handoff, walk it through these explicit states and confirm the behavior:1231. **Fresh build present:** the launcher opens the packaged app and writes a timestamped log entry.1242. **Stale build:** with source newer than the build pointer, the launcher refuses to open and prompts a rebuild.1253. **Missing build:** with no packaged output, the launcher reports the build command and does not fall back to dev.1264. **Failed build:** with the build command erroring, the launcher reports the failing command and log path and opens nothing.1275. **Update rollback:** after a simulated failed update, the previous build is still intact and launchable.1286. **Clean shutdown:** closing the app or launcher leaves no orphaned main process.129130## Required output131132Return:1331. **Launch mode:** packaged versus dev, and whether it matches intent.1342. **Build-freshness verdict:** fresh, stale, or missing, with the evidence used.1353. **Fail-closed assessment:** would a stale or failed build still open an old app?1364. **Launcher script issues:** `file:line | issue | fix`.1375. **Logging and update-safety findings.**1386. **Next safe command:** usually the production build or package.139140## Safety141142- Never recommend opening an older packaged app when the current build is missing or its build failed — fail closed and report the failing command plus the log path.143- Do not run a destructive or long package build silently; recommend it and let the owner trigger it.144- No hardcoded machine-specific home paths — keep launchers portable across machines.145- Redact any secrets surfaced in launcher env or logs.146- Do not delete or overwrite an existing packaged build during review.147148## Completion criteria149150Done means the launch mode is confirmed against intent; build freshness is judged from evidence; the fail-closed rule is verified (a stale or failed build cannot open an old app); launcher-script and update-safety issues are listed with `file:line` fixes; and the safe next command (build or package) is named.