makecindy / makecindy/cindy

macOS auto-update can create nested Cindy.app and invalidate the outer bundle

Open
#3,816 2 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.