From 654e4529d532ee3dab4d7f5f5aa32e62c39d3356 Mon Sep 17 00:00:00 2001 From: Pavel Soukenik Date: Tue, 26 May 2026 19:33:18 -0700 Subject: [PATCH] fix: refresh branch in footer on WSL /mnt repos --- .../src/core/footer-data-provider.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/src/core/footer-data-provider.ts b/packages/coding-agent/src/core/footer-data-provider.ts index b7ea09b8..f15001c2 100644 --- a/packages/coding-agent/src/core/footer-data-provider.ts +++ b/packages/coding-agent/src/core/footer-data-provider.ts @@ -1,5 +1,5 @@ import { type ExecFileException, execFile, spawnSync } from "child_process"; -import { existsSync, type FSWatcher, readFileSync, statSync, unwatchFile, watchFile } from "fs"; +import { existsSync, type FSWatcher, readFileSync, type Stats, statSync, unwatchFile, watchFile } from "fs"; import { dirname, join, resolve } from "path"; import { closeWatcher, FS_WATCH_RETRY_DELAY_MS, watchWithErrorHandler } from "../utils/fs-watch.ts"; @@ -80,6 +80,18 @@ function resolveBranchWithGitAsync(repoDir: string): Promise { }); } +function isWslEnvironment(): boolean { + return process.platform === "linux" && !!(process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP); +} + +function isWindowsMountedRepoPath(repoDir: string): boolean { + return /^\/mnt\/[a-z](?:\/|$)/i.test(repoDir); +} + +function shouldPollGitHead(repoDir: string): boolean { + return isWslEnvironment() && isWindowsMountedRepoPath(repoDir); +} + /** * Provides git branch and extension statuses - data not otherwise accessible to extensions. * Token stats, model info available via ctx.sessionManager and ctx.model. @@ -92,6 +104,8 @@ export class FooterDataProvider { private cachedBranch: string | null | undefined = undefined; private gitPaths: GitPaths | null | undefined = undefined; private headWatcher: FSWatcher | null = null; + private headWatchFilePath: string | null = null; + private headWatchFileListener: ((current: Stats, previous: Stats) => void) | null = null; private reftableWatcher: FSWatcher | null = null; private reftableTablesListWatcher: FSWatcher | null = null; private reftableTablesListPath: string | null = null; @@ -255,6 +269,11 @@ export class FooterDataProvider { private clearGitWatchers(): void { closeWatcher(this.headWatcher); this.headWatcher = null; + if (this.headWatchFilePath && this.headWatchFileListener) { + unwatchFile(this.headWatchFilePath, this.headWatchFileListener); + this.headWatchFilePath = null; + this.headWatchFileListener = null; + } closeWatcher(this.reftableWatcher); this.reftableWatcher = null; closeWatcher(this.reftableTablesListWatcher); @@ -289,6 +308,8 @@ export class FooterDataProvider { this.clearGitWatchers(); if (!this.gitPaths) return; + const pollGitHead = shouldPollGitHead(this.gitPaths.repoDir); + // Watch the directory containing HEAD, not HEAD itself. // Git uses atomic writes (write temp, rename over HEAD), which changes the inode. // fs.watch on a file stops working after the inode changes. @@ -301,7 +322,20 @@ export class FooterDataProvider { }, () => this.handleGitWatcherError(), ); - if (!this.headWatcher) { + if (pollGitHead) { + this.headWatchFilePath = this.gitPaths.headPath; + this.headWatchFileListener = (current, previous) => { + if ( + current.mtimeMs !== previous.mtimeMs || + current.ctimeMs !== previous.ctimeMs || + current.size !== previous.size + ) { + this.scheduleRefresh(); + } + }; + watchFile(this.headWatchFilePath, { interval: 1000 }, this.headWatchFileListener); + } + if (!this.headWatcher && !pollGitHead) { return; }