Files
hermes-agent/apps/desktop/electron/bundle-swap.test.ts
T
Brooklyn Nicholson c19537fb03 fix(desktop): relaunch into the swapped bundle instead of booting a torn renderer
A user who reopens Hermes while an update is running lands on the boot gate,
which is what it is for. But the updater swaps the packaged bundle on disk
after `hermes update` exits, and its `open` leg only focuses this already-
running process, so nothing ever loads the new build. The parked instance then
passes the gate and boots the new runtime under the old renderer — the "App
build out of date" banner immediately after a fully successful update, over an
Updates card that says "You're on the latest version" and so offers no remedy.

Compare the install stamp this process loaded at boot with the one on disk when
the gate clears. On positive proof of a swap — different commit, or a different
builtAt at the same commit — relaunch instead of starting a backend. Detection
fails quiet like bundle-skew, so a swap that never happened (the Windows
locked-binary case) is unchanged. A one-shot argv flag makes a relaunch loop
impossible and a 15s failsafe falls back to the old behavior.

Co-authored-by: tk-pkm111 <133480534+tk-pkm111@users.noreply.github.com>
Co-authored-by: aeonsong <aeonsong@users.noreply.github.com>
2026-09-01 22:33:29 -05:00

50 lines
1.9 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { detectBundleSwap } from './bundle-swap'
const RUNNING = { builtAt: '2026-08-29T04:00:00.000Z', commit: 'a'.repeat(40), source: 'local' }
describe('detectBundleSwap', () => {
it('reports a swap when the on-disk stamp carries a different commit', () => {
const onDisk = { ...RUNNING, commit: 'b'.repeat(40) }
expect(detectBundleSwap(RUNNING, onDisk)).toBe(true)
})
it('reports a swap when the same commit was rebuilt (builtAt moved)', () => {
const onDisk = { ...RUNNING, builtAt: '2026-08-31T23:55:41.149Z' }
expect(detectBundleSwap(RUNNING, onDisk)).toBe(true)
})
// The Windows locked-binary case (#92233): the swap leg failed, so the
// bundle on disk is still the one we are running. A relaunch would repair
// nothing and cost the user their window.
it('is quiet when the on-disk stamp matches the running one', () => {
expect(detectBundleSwap(RUNNING, { ...RUNNING })).toBe(false)
})
it('is quiet without a running stamp (dev runs)', () => {
expect(detectBundleSwap(null, { ...RUNNING })).toBe(false)
})
it('is quiet without an on-disk stamp (unreadable resources)', () => {
expect(detectBundleSwap(RUNNING, null)).toBe(false)
})
it('is quiet on a fallback stamp on either side (non-git build)', () => {
const fallbackTagged = { ...RUNNING, source: 'fallback' }
const fallbackCommit = { ...RUNNING, commit: '0'.repeat(40) }
expect(detectBundleSwap(fallbackTagged, { ...RUNNING, commit: 'b'.repeat(40) })).toBe(false)
expect(detectBundleSwap(RUNNING, fallbackCommit)).toBe(false)
})
it('treats a missing builtAt on either side as unprovable at the same commit', () => {
const noBuiltAt = { commit: RUNNING.commit, source: 'local' }
expect(detectBundleSwap(noBuiltAt, { ...RUNNING })).toBe(false)
expect(detectBundleSwap(RUNNING, noBuiltAt)).toBe(false)
})
})