8.7 KiB
Auto-update
The desktop shell (app/src-tauri) auto-updates itself via Tauri's
plugin-updater against a manifest
published on GitHub Releases. The OpenHuman core sidecar (openhuman binary)
ships inside the .app bundle, so a shell update upgrades both.
Architecture
| Piece | Role |
|---|---|
app/src-tauri/Cargo.toml |
declares tauri-plugin-updater |
app/src-tauri/tauri.conf.json (plugins.updater) |
endpoint + minisign pubkey |
app/src-tauri/permissions/allow-app-update.toml |
ACL allow-list for the four updater commands |
app/src-tauri/src/lib.rs::check_app_update |
probe-only; returns version info |
app/src-tauri/src/lib.rs::download_app_update |
downloads bundle bytes, stages them, does NOT install |
app/src-tauri/src/lib.rs::install_app_update |
installs previously-staged bytes + relaunches |
app/src-tauri/src/lib.rs::apply_app_update |
legacy combined download+install+restart (kept for compat) |
app/src/hooks/useAppUpdate.ts |
React state machine, auto-check, auto-download |
app/src/components/AppUpdatePrompt.tsx |
global banner (mounted in App.tsx) — silent during download |
app/src/components/settings/panels/AboutPanel.tsx |
manual "Check for updates" |
.github/workflows/release.yml |
builds + signs + publishes latest.json |
The shell emits two Tauri events while updating:
app-update:status— string payload, one ofchecking,downloading,ready_to_install,installing,restarting,up_to_date,errorapp-update:progress—{ chunk: number, total: number | null }
useAppUpdate listens on both and exposes a state machine
(idle | checking | available | downloading | ready_to_install | installing | restarting | up_to_date | error).
User flow (Option 2: auto-download, prompt to restart)
- ~5 seconds after launch, the hook runs a silent
check_app_update. It re-checks every 15 minutes. - If the manifest reports a newer version, the hook automatically calls
download_app_updatein the background — the user sees nothing. - Once the bytes are staged, the Rust side emits
ready_to_installand the bottom-right banner appears with the header "Update ready to install" and a body line of "Version is ready to install." (falling back to "A new version is ready to install." when the manifest didn't supply a version), followed by Restart now / Later buttons. - Clicking Restart now invokes
install_app_update, which acquires the core restart lock, shuts down the in-process core, callsUpdate::install(staged_bytes)(no re-download), and thenapp.restart(). - Later dismisses the banner without canceling the staged bytes — the user can also click "Check for updates" in Settings → About to surface the prompt again on demand.
Why this flow vs. silent install: a chat / AI app often has in-flight conversations and background agent work. Yanking the process away mid-task costs more user trust than a one-click "Restart now" prompt earns in convenience. We download invisibly so the only action the user takes is choosing the restart moment.
Validating end-to-end (issue #677 acceptance criteria)
The auto-update path must be validated against a real signed bundle and a
real latest.json — pnpm tauri dev does not produce updater-compatible
artifacts. Use this recipe.
Prerequisites
- A published GitHub release at a higher version than what you'll build
locally (e.g. the latest
v0.53.x). The release must include the signed bundle for your platform pluslatest.json. - No signing key needed for verification. Minisign signature
verification on the downloaded bundle uses the public key already baked
into
tauri.conf.json::plugins.updater.pubkey, which the local build picks up automatically.TAURI_SIGNING_PRIVATE_KEY(+ its password) is required only by CI to sign new releases — never to verify existing ones. Treat the private key as a secret and keep it out of dev machines.
Recipe
-
Pick a target older than the published release. Edit all four version sources to a known-older value (e.g.
0.53.0if0.53.4is published):app/package.json::version app/src-tauri/Cargo.toml::package.version app/src-tauri/tauri.conf.json::version Cargo.toml::workspace.package.versionscripts/release/verify-version-sync.jsexists exactly to keep these four in lockstep — run it after editing. -
Build a packaged bundle locally.
pnpm --filter openhuman-app tauri:ensure # vendored CEF-aware tauri-cli pnpm --filter openhuman-app tauri:build:ui # exports CEF_PATH + builds the .apptauri:build:uiexportsCEF_PATH=~/Library/Caches/tauri-cefbefore runningcargo tauri build— the bundler needs this to copyChromium Embedded Framework.frameworkintoContents/Frameworks/. A barepnpm tauri buildskips that step and the resulting binary panics incef::library_loader::LibraryLoader::new.On macOS the artifact lands in
app/src-tauri/target/release/bundle/macos/OpenHuman.app. -
Run the packaged build.
open app/src-tauri/target/release/bundle/macos/OpenHuman.app # or, with Rust + frontend logs in the terminal: ./app/src-tauri/target/release/bundle/macos/OpenHuman.app/Contents/MacOS/OpenHumanYou should see
[app-update]lines start to flow ~5 seconds after launch (auto-check), or immediately after clicking Settings → About → Check for updates. -
Trigger the check — either wait ~5s for the auto-check, or open Settings → About → Check for updates. The check is silent; the prompt appears only once the download has staged.
-
Watch the auto-download flow (fires automatically — no click needed to start the download). Expected log sequence:
[app-update] check requested (current: <old-version>)[app-update] update available: <old> -> <new>[app-update] download_app_update invoked from frontend[app-update] downloading <new-version> (background)[app-update] download complete — staging for install[app-update] staged <new-version> — awaiting user-initiated install
At this point the bottom-right banner appears with the header "Update ready to install" and a body line of "Version is ready to install." (or "A new version is ready to install." as the fallback when the manifest didn't supply a version string), followed by Restart now / Later buttons.
-
Click "Restart now". Expected log sequence:
[app-update] install_app_update invoked from frontend[app-update] installing staged version <new-version>[app-update] install complete — relaunching
The app relaunches itself; the new bundle's version (in Settings → About) should match the published release.
-
Confirm the core sidecar came back up.
[core]log lines should appear after relaunch andcore_rpccalls from the UI must succeed.
Troubleshooting
- "signature did not verify" — the local bundle was built with a
different signing key than the one whose pubkey is in
tauri.conf.json. Rebuild against the sameTAURI_SIGNING_PRIVATE_KEYused by the release workflow, or temporarily swap the pubkey while testing. - "endpoint did not return a valid JSON manifest" — the redirect from
releases/latest/download/latest.jsonresolved to a release that lacks the asset. Confirm the latest non-draft release on GitHub haslatest.jsonattached (jobpublish-updater-manifest). - Updater doesn't fire in dev —
pnpm tauri devsetsbundle.createUpdaterArtifacts: false(seescripts/prepareTauriConfig.js), so the dev profile never produces a bundle the updater can swap in. Usepnpm tauri build. - The banner never shows on first launch — that's expected; the initial probe is delayed 5s. To force it, click "Check for updates" in the About panel.
Logs
- Rust side:
log::info!("[app-update] ...")/log::warn!/log::error! - Frontend:
console.debug('[app-update] ...')and friends
Both prefixes are stable and grep-friendly per CLAUDE.md.