macOS auto-update can create nested Cindy.app and invalidate the outer bundle
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 395
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## Description
On macOS, an in-app auto-update can leave Cindy in this layout:
```text
/Applications/Cindy.app/
└── Cindy.app/
└── Contents/
```
The outer `Cindy.app` therefore has no `Contents` directory and macOS rejects it:
```text
/Applications/Cindy.app: bundle format unrecognized, invalid, or unsuitable
```
Both `spctl --assess --type execute` and `codesign --verify --deep --strict` fail for the outer bundle after the update.
## Reproduction
1. Install Cindy as `/Applications/Cindy.app`.
2. Start the app and let the auto-updater apply a downloaded update.
3. After the updater exits/restarts Cindy, the outer bundle contains only a nested `Cindy.app`.
Manually moving the nested bundle back to `/Applications/Cindy.app` restores the app, but a subsequent auto-update can reproduce the same layout.
## Likely cause
The generated macOS replacement script removes the old bundle, then moves the extracted bundle to the same path:
```bash
rm -rf "${appPath}" 2>>"${logPath}"
mv "$NEW_APP" "${appPath}" 2>>"${logPath}"
```
(`apps/desktop/src/main/updateScriptMacOS.ts`, current main around lines 148-155.)
The `rm -rf` exit code is not checked. If removal fails for any reason, `appPath` still exists as a directory. POSIX `mv` then treats that existing directory as the destination directory and moves the extracted bundle inside it:
```text
/Applications/Cindy.app/Cindy.app/
```
The subsequent success check only verifies that `appPath` is a directory. It does not verify `appPath/Contents`, so it cannot detect the nested-bundle result.
## Suggested fix
Make the replacement failure-safe and never move the new bundle into a pre-existing destination:
- Rename the old bundle to a sibling backup path on the same volume.
- Move the extracted bundle into the now-vacant original path.
- If installing the new bundle fails, rename the backup back.
- Validate `appPath/Contents` before declaring success.
- Best-effort cleanup of the old bundle after the new bundle is installed.
Checking `rm -rf` alone would prevent the invalid nested layout, but would still leave the old app unavailable after a failed deletion. An old-bundle rename plus rollback preserves the previous install on installation failure.
## Environment
- macOS
- Install location: `/Applications/Cindy.app`
- Failure occurs specifically during Cindy's in-app update flow
- App bundle size after failed update: approximately 623 MB
This appears to be an updater replacement-path/failure-handling issue rather than Gatekeeper, quarantine, or code signing, because the outer bundle lacks `Contents` entirely.
Contributor guide
Research direction
Start in apps/desktop/src/main/updateScriptMacOS.ts around lines 148-155 and trace how the generated replacement script handles the old and extracted bundles. Make the replacement preserve a rollback path, prevent a pre-existing destination from producing a nested Cindy.app, validate appPath/Contents, and clean up the old bundle only after successful installation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- electron, macos, typescript
- Domain
- desktop, release
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100