mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): prevent stale worktree status (#69781)
Clear the coding rail while a session's workspace changes and discard late Git status results for the previous cwd. This prevents Ctrl+Shift+B from opening a worktree dialog against an old branch.
This commit is contained in:
parent
4baf2ed8ad
commit
9f5e568812
2 changed files with 48 additions and 6 deletions
|
|
@ -76,6 +76,31 @@ describe('refreshRepoStatus', () => {
|
|||
expect($repoStatus.get()).toBeNull()
|
||||
})
|
||||
|
||||
it('never publishes an old worktree status after the active cwd moves', async () => {
|
||||
let resolveOld!: (status: HermesRepoStatus | null) => void
|
||||
stubProbe(
|
||||
() =>
|
||||
new Promise(resolve => {
|
||||
resolveOld = resolve
|
||||
})
|
||||
)
|
||||
|
||||
$currentCwd.set('/repo-a')
|
||||
vi.advanceTimersByTime(200)
|
||||
await vi.runAllTicks()
|
||||
|
||||
// The first probe is still in flight when the user switches sessions. The
|
||||
// new cwd's probe is intentionally debounced, so this is the exact window
|
||||
// where Ctrl+Shift+B used to see the old branch in the coding rail.
|
||||
$currentCwd.set('/repo-b')
|
||||
expect($repoStatus.get()).toBeNull()
|
||||
|
||||
resolveOld(sampleStatus)
|
||||
await vi.runAllTicks()
|
||||
|
||||
expect($repoStatus.get()).toBeNull()
|
||||
})
|
||||
|
||||
it('runs one probe at a time and coalesces overlap into one trailing refresh', async () => {
|
||||
const resolvers: Array<(status: HermesRepoStatus | null) => void> = []
|
||||
const calls: string[] = []
|
||||
|
|
|
|||
|
|
@ -56,11 +56,11 @@ async function loadWorktrees(target: string): Promise<void> {
|
|||
try {
|
||||
const worktrees = await list(target)
|
||||
|
||||
if (inflightCwd === target) {
|
||||
if (inflightCwd === target && statusStillBelongsToActiveCwd(target)) {
|
||||
$repoWorktrees.set(worktrees)
|
||||
}
|
||||
} catch {
|
||||
if (inflightCwd === target) {
|
||||
if (inflightCwd === target && statusStillBelongsToActiveCwd(target)) {
|
||||
$repoWorktrees.set([])
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +84,16 @@ let repoStatusRefreshTimer: ReturnType<typeof setTimeout> | null = null
|
|||
|
||||
const normalizeCwd = (cwd?: null | string): null | string => cwd?.trim() || null
|
||||
|
||||
// A result only belongs in the global rail while it still describes the active
|
||||
// workspace. The debounce below deliberately delays the next probe; without
|
||||
// this live check, an old probe can land during that gap and briefly make a new
|
||||
// session look like it is on the previous worktree's branch.
|
||||
const statusStillBelongsToActiveCwd = (target: string): boolean => {
|
||||
const active = normalizeCwd($currentCwd.get())
|
||||
|
||||
return !active || active === target
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-probe the working tree for `cwd` (defaults to the active session's cwd).
|
||||
* Best-effort: a non-repo, a remote backend, or a missing probe clears the
|
||||
|
|
@ -95,7 +105,7 @@ async function runRepoStatusRefresh({ probe, seq, target }: RepoStatusRefreshReq
|
|||
|
||||
// Drop the result if the cwd moved on while we were probing (a fast session
|
||||
// switch) — the newer probe owns the atom.
|
||||
if (seq === repoStatusRefreshSeq && inflightCwd === target) {
|
||||
if (seq === repoStatusRefreshSeq && inflightCwd === target && statusStillBelongsToActiveCwd(target)) {
|
||||
$repoStatus.set(status)
|
||||
|
||||
// Worktrees only matter inside a repo; clear them otherwise.
|
||||
|
|
@ -106,7 +116,7 @@ async function runRepoStatusRefresh({ probe, seq, target }: RepoStatusRefreshReq
|
|||
}
|
||||
}
|
||||
} catch {
|
||||
if (seq === repoStatusRefreshSeq && inflightCwd === target) {
|
||||
if (seq === repoStatusRefreshSeq && inflightCwd === target && statusStillBelongsToActiveCwd(target)) {
|
||||
$repoStatus.set(null)
|
||||
$repoWorktrees.set([])
|
||||
}
|
||||
|
|
@ -169,8 +179,15 @@ function scheduleRepoStatusRefresh(cwd?: null | string): void {
|
|||
// Wired once at module load (mirrors projects.ts's module-scope subscriptions).
|
||||
// Each is a structural edge where the working tree may have changed under us.
|
||||
|
||||
// The active session's cwd changed (session switch / new chat) → re-probe.
|
||||
$currentCwd.subscribe(cwd => scheduleRepoStatusRefresh(cwd))
|
||||
// The active session's cwd changed (session switch / new chat) → immediately
|
||||
// hide the old repo's facts, then re-probe after the small debounce. This makes
|
||||
// keyboard actions safe in the switch-to-probe gap: Ctrl+Shift+B cannot use a
|
||||
// still-painted branch label from the previous worktree.
|
||||
$currentCwd.subscribe(cwd => {
|
||||
$repoStatus.set(null)
|
||||
$repoWorktrees.set([])
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue