Proposal: in-place desktop auto-update (cached zip + Restart-now, t3code-style)
- Dominant language
- Rust
- Stars
- 54.2k
- Forks
- 6.2k
- Avg merge
- 3d 2h
- Merged PRs (30d)
- 262
Description
## What problem would this solve?
The desktop app's current auto-update leaves a new zip/duplicate bundle and doesn't reliably replace the running app in its existing location. Users on macOS (`/Applications`) and Windows (`Program Files`) see extra artifacts, manual steps, and confusing restart. Inspired by https://github.com/pingdotgg/t3code, we want a restart-and-replace flow that overwrites the same `.app`/install dir and relaunches.
Affects all desktop users on macOS + Windows (Linux deb/rpm/flatpak delegated to package manager).
## What would a good outcome look like?
- "Check for updates" downloads once to `userData/update-cache/.zip` (pruned to last 2 for rollback), extracts to `os.tmpdir()`, then swaps in-place over the existing install and relaunches the same `Goose.app`/`Goose.exe` path.
- No zip/duplicate left in `~/Downloads` or install parent after update; cache holds <=2 zips.
- Update shows a **"Restart now" Notification** (user-confirmed) on `update-downloaded` for both `electron-updater` and GitHub-fallback paths -- click triggers the swap + `app.quit()`/`quitAndInstall`.
- If the install location is not writable, fail gracefully with a permission Notification ("move to /Applications or check write permission") -- no UAC/osascript elevation in v1.
- `codesign --verify --deep` logged on macOS (non-blocking) to preserve Gatekeeper.
- Existing `autoUpdater` events (`checking-for-update`, `update-available`, `download-progress`, `update-downloaded`, `error`) still fire.
## Possible approaches
**Source:** `docs/brainstorms/2026-09-02-desktop-autoupdate-inplace-restart.md` + `docs/plans/2026-09-02-desktop-autoupdate-inplace-restart.md` (Approach A -- minimal, t3code-inspired)
- **Approach A (recommended, S effort):** Keep `electron-updater` as primary (`quitAndInstall(false,true)` already does Squirrel in-place). Make `githubUpdater.ts` the canonical zip-free path: `ensureCacheDir()`/`pruneCache()`/`isTargetWritable()`, `ditto -x -k` (macOS) / `Expand-Archive` (Windows), swap via detached script + `app.relaunch`, keep zip cached. ~120 lines in 2 files.
- **Approach B:** Vendor t3code's helper verbatim (`rsync`/`ditto` detached script) -- more faithful but `rsync` doesn't preserve xattrs/signing, larger surface.
- **Approach C:** Go full `electron-updater` differential and drop GitHub fallback -- cleanest long-term but loses fallback.
Implementation touches `ui/desktop/src/utils/githubUpdater.ts` and `ui/desktop/src/utils/autoUpdater.ts` only (Forge and `main.ts` unchanged).
## Additional context
- Decision log: macOS + Windows in scope, Restart-now UX, keep cached zip for rollback, graceful-fail permissions.
- **Minimal PR (code-only, 2 files, 122 lines):** https://github.com/aaif-goose/goose/pull/11777 -- implements Approach A, rebased on `main` @ f9176266e, `npx tsc --noEmit` OK
- **Full PR with docs (4 files, 240 lines):** https://github.com/aaif-goose/goose/pull/11775
- Plan written: `docs/plans/2026-09-02-desktop-autoupdate-inplace-restart.md` (and brainstorm `docs/brainstorms/2026-09-02-desktop-autoupdate-inplace-restart.md`)
- Verification: `npx tsc --noEmit` OK, `cargo fmt`/`clippy` OK; manual smoke needed (install vN -> vN+1 -> Restart now -> version bump).
Do not begin implementation until the issue reaches Ready on the Goose Issues board (https://github.com/orgs/aaif-goose/projects/1).
Closes via PRs above once Ready.
Contributor guide
Assessment
This issue has not been assessed yet.