fix(windows): skip watcher-process reauthorization during removal cleanup (#26765)

Windows runtime file watches run in-process, so closeFileExplorerWatchersForPath
never has a watcher child to close there. Re-resolving the authorized path after
Git already dropped an external sibling worktree's registration threw
'outside allowed directories' and aborted the Windows partial-removal recovery.

Fixes #26764

Co-authored-by: Neil <neil@stably.ai>
This commit is contained in:
Naveen Chatlapalli
2026-10-26 08:17:19 -07:00
committed by GitHub
co-authored by Neil
parent 396e3f1b2f
commit 67cdeada9d
2 changed files with 23 additions and 1 deletions
+2 -1
View File
@@ -94,7 +94,8 @@ export class RuntimeFileWatches {
if (leases) {
await Promise.all(Array.from(leases, (lease) => lease.suspend()))
}
if (!connectionId) {
// Windows watches in-process, so only the leases above exist; Git may already have revoked the path.
if (!connectionId && process.platform !== 'win32') {
// Failed setup may still own a child without having published a lease-release callback.
const resolvedRootPath = await resolveAuthorizedPath(rootPath, this.host.requireStore())
await closeFileExplorerWatcherInWatcherProcess(resolvedRootPath)
@@ -212,6 +212,27 @@ describe('RuntimeFileCommands file watching', () => {
expect(vi.getTimerCount()).toBe(0)
})
it('closes Windows removal watchers without reauthorizing a path Git already unregistered', async () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
const rootPath = 'C:\\projects\\repo-feature'
const watcher = createWindowsWatcher(() => {
queueMicrotask(() => watcher.emit('close'))
})
watchMock.mockReturnValue(watcher)
resolveAuthorizedPathMock.mockResolvedValue(rootPath)
statMock.mockResolvedValue({ isDirectory: () => true })
const { commands } = createRuntimeFileCommands(rootPath)
await commands.watchFileExplorer('id:wt-1', vi.fn())
resolveAuthorizedPathMock.mockRejectedValue(
new Error('Access denied: path resolves outside allowed directories')
)
await expect(commands.closeFileExplorerWatchersForPath(rootPath)).resolves.toBeUndefined()
expect(watcher.close).toHaveBeenCalledTimes(1)
expect(closeWatcherInWatcherProcessMock).not.toHaveBeenCalled()
})
it('retains Windows close ownership until late physical exit without retry leaks', async () => {
Object.defineProperty(process, 'platform', { configurable: true, value: 'win32' })
const watcher = createWindowsWatcher(() => undefined)