From e2fd8a37dca030189ee4cdecaef96eb89b9f49eb Mon Sep 17 00:00:00 2001 From: ethernet Date: Mon, 20 Jul 2026 16:01:28 -0400 Subject: [PATCH] fix(desktop): refresh repo status on session switch with unchanged cwd (#68208) fix(desktop): refresh repo status on session switch with unchanged cwd --- apps/desktop/src/store/coding-status.test.ts | 26 +++++++++++++++++++- apps/desktop/src/store/coding-status.ts | 9 ++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/store/coding-status.test.ts b/apps/desktop/src/store/coding-status.test.ts index d83e31a4849e..7d1915095f07 100644 --- a/apps/desktop/src/store/coding-status.test.ts +++ b/apps/desktop/src/store/coding-status.test.ts @@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { HermesRepoStatus } from '@/global' import { $repoStatus, $repoStatusLoading, refreshRepoStatus } from './coding-status' -import { $currentCwd } from './session' +import { $currentCwd, $selectedStoredSessionId } from './session' const sampleStatus: HermesRepoStatus = { branch: 'feature/login', @@ -30,6 +30,7 @@ describe('refreshRepoStatus', () => { vi.useFakeTimers() $repoStatus.set(null) $currentCwd.set('') + $selectedStoredSessionId.set(null) delete (window as unknown as { hermesDesktop?: unknown }).hermesDesktop }) @@ -117,4 +118,27 @@ describe('refreshRepoStatus', () => { expect($repoStatus.get()).toEqual(sampleStatus) expect($repoStatusLoading.get()).toBe(false) }) + + it('refreshes when the stored session id changes even if the cwd is unchanged', async () => { + const probe = vi.fn(async () => sampleStatus) + stubProbe(probe) + + $currentCwd.set('/repo') + $selectedStoredSessionId.set('session-a') + // The cwd subscription fires on the set above; drain the debounced refresh. + vi.advanceTimersByTime(200) + await vi.runAllTicks() + + probe.mockClear() + + // Switch to a different session in the SAME repo dir. The cwd atom value is + // identical, so its subscription would not re-fire — but the stored-session + // id did change, which must still trigger a probe so the branch label + // tracks the new session's checked-out branch. + $selectedStoredSessionId.set('session-b') + vi.advanceTimersByTime(200) + await vi.runAllTicks() + + expect(probe).toHaveBeenCalledWith('/repo') + }) }) diff --git a/apps/desktop/src/store/coding-status.ts b/apps/desktop/src/store/coding-status.ts index 84f9c9b2aea5..0ed7eed5626d 100644 --- a/apps/desktop/src/store/coding-status.ts +++ b/apps/desktop/src/store/coding-status.ts @@ -4,7 +4,7 @@ import type { HermesGitWorktree, HermesRepoStatus } from '@/global' import { desktopGit } from '@/lib/desktop-git' import { $worktreeRefreshToken } from './projects' -import { $busy, $currentCwd } from './session' +import { $busy, $currentCwd, $selectedStoredSessionId } from './session' import { $workspaceChangeTick } from './workspace-events' // Live working-tree status for the active session's cwd — the data backbone of @@ -172,6 +172,13 @@ function scheduleRepoStatusRefresh(cwd?: null | string): void { // The active session's cwd changed (session switch / new chat) → re-probe. $currentCwd.subscribe(cwd => scheduleRepoStatusRefresh(cwd)) +// Switching sessions can land on the same cwd but a different checked-out +// branch (the agent ran `git checkout` in another session's terminal). The cwd +// subscription above won't fire when the path is identical, so the branch label +// would stay stale until a window focus or turn-settle triggers a refresh. +// Treat the stored-session id as a structural edge in its own right. +$selectedStoredSessionId.subscribe(() => scheduleRepoStatusRefresh()) + // A worktree add/remove (desktop op, or the agent's out-of-band git in a settled // turn / a window refocus — both already bump this token) → re-probe. $worktreeRefreshToken.subscribe(() => scheduleRepoStatusRefresh())