Skip to content

Commit 8c46d57

Browse files
Nat Torkingtonclaude
authored andcommitted
feat: condense watcher warnings and add polling fallback for unwatchable dirs
WSL UNC paths can't be watched by fsnotify (ReadDirectoryChangesW fails). Previously WatchRecursive logged one warning per subdirectory, producing dozens of noisy lines. Now it returns watched/unwatched counts silently, the caller prints a single condensed summary per root, and a 2-minute polling goroutine supplements the 15-minute periodic sync for faster change detection on unwatchable paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a0dde3f commit 8c46d57

File tree

3 files changed

+53
-29
lines changed

3 files changed

+53
-29
lines changed

cmd/agentsview/main.go

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ var (
2626
)
2727

2828
const (
29-
periodicSyncInterval = 15 * time.Minute
30-
watcherDebounce = 500 * time.Millisecond
31-
browserPollInterval = 100 * time.Millisecond
32-
browserPollAttempts = 60
29+
periodicSyncInterval = 15 * time.Minute
30+
unwatchedPollInterval = 2 * time.Minute
31+
watcherDebounce = 500 * time.Millisecond
32+
browserPollInterval = 100 * time.Millisecond
33+
browserPollAttempts = 60
3334
)
3435

3536
func main() {
@@ -145,10 +146,13 @@ func runServe(args []string) {
145146

146147
runInitialSync(engine)
147148

148-
stopWatcher := startFileWatcher(cfg, engine)
149+
stopWatcher, unwatchedDirs := startFileWatcher(cfg, engine)
149150
defer stopWatcher()
150151

151152
go startPeriodicSync(engine)
153+
if len(unwatchedDirs) > 0 {
154+
go startUnwatchedPoll(engine)
155+
}
152156

153157
port := server.FindAvailablePort(cfg.Host, cfg.Port)
154158
if port != cfg.Port {
@@ -244,50 +248,65 @@ func printSyncProgress(p sync.Progress) {
244248

245249
func startFileWatcher(
246250
cfg config.Config, engine *sync.Engine,
247-
) func() {
251+
) (stopWatcher func(), unwatchedDirs []string) {
248252
t := time.Now()
249253
onChange := func(paths []string) {
250254
engine.SyncPaths(paths)
251255
}
252256
watcher, err := sync.NewWatcher(watcherDebounce, onChange)
253257
if err != nil {
254258
log.Printf("warning: file watcher unavailable: %v", err)
255-
return func() {}
259+
return func() {}, nil
260+
}
261+
262+
type watchRoot struct {
263+
dir string
264+
root string // actual path passed to WatchRecursive
256265
}
257266

258-
var dirs int
267+
var roots []watchRoot
259268
for _, d := range cfg.ResolveClaudeDirs() {
260269
if _, err := os.Stat(d); err == nil {
261-
n, _ := watcher.WatchRecursive(d)
262-
dirs += n
270+
roots = append(roots, watchRoot{d, d})
263271
}
264272
}
265273
for _, d := range cfg.ResolveCodexDirs() {
266274
if _, err := os.Stat(d); err == nil {
267-
n, _ := watcher.WatchRecursive(d)
268-
dirs += n
275+
roots = append(roots, watchRoot{d, d})
269276
}
270277
}
271278
for _, d := range cfg.ResolveCopilotDirs() {
272279
copilotState := filepath.Join(d, "session-state")
273280
if _, err := os.Stat(copilotState); err == nil {
274-
n, _ := watcher.WatchRecursive(copilotState)
275-
dirs += n
281+
roots = append(roots, watchRoot{d, copilotState})
276282
}
277283
}
278284
for _, d := range cfg.ResolveGeminiDirs() {
279285
geminiTmp := filepath.Join(d, "tmp")
280286
if _, err := os.Stat(geminiTmp); err == nil {
281-
n, _ := watcher.WatchRecursive(geminiTmp)
282-
dirs += n
287+
roots = append(roots, watchRoot{d, geminiTmp})
283288
}
284289
}
290+
291+
var totalWatched int
292+
for _, r := range roots {
293+
watched, uw, _ := watcher.WatchRecursive(r.root)
294+
totalWatched += watched
295+
if uw > 0 {
296+
unwatchedDirs = append(unwatchedDirs, r.dir)
297+
log.Printf(
298+
"Couldn't watch %d directories under %s, will poll every %s",
299+
uw, r.dir, unwatchedPollInterval,
300+
)
301+
}
302+
}
303+
285304
fmt.Printf(
286305
"Watching %d directories for changes (%s)\n",
287-
dirs, time.Since(t).Round(time.Millisecond),
306+
totalWatched, time.Since(t).Round(time.Millisecond),
288307
)
289308
watcher.Start()
290-
return watcher.Stop
309+
return watcher.Stop, unwatchedDirs
291310
}
292311

293312
func startPeriodicSync(engine *sync.Engine) {
@@ -299,6 +318,15 @@ func startPeriodicSync(engine *sync.Engine) {
299318
}
300319
}
301320

321+
func startUnwatchedPoll(engine *sync.Engine) {
322+
ticker := time.NewTicker(unwatchedPollInterval)
323+
defer ticker.Stop()
324+
for range ticker.C {
325+
log.Println("Polling unwatched directories...")
326+
engine.SyncAll(nil)
327+
}
328+
}
329+
302330
func openBrowser(url string) {
303331
for range browserPollAttempts {
304332
time.Sleep(browserPollInterval)

internal/sync/watcher.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,27 +52,23 @@ func NewWatcher(
5252

5353
// WatchRecursive walks a directory tree and adds all
5454
// subdirectories to the watch list. Returns the number
55-
// of directories added.
56-
func (w *Watcher) WatchRecursive(root string) (int, error) {
57-
var count int
58-
err := filepath.WalkDir(root,
55+
// of directories watched and unwatched (failed to add).
56+
func (w *Watcher) WatchRecursive(root string) (watched int, unwatched int, err error) {
57+
err = filepath.WalkDir(root,
5958
func(path string, d fs.DirEntry, err error) error {
6059
if err != nil {
6160
return nil // skip inaccessible dirs
6261
}
6362
if d.IsDir() {
6463
if addErr := w.watcher.Add(path); addErr != nil {
65-
log.Printf(
66-
"watcher: cannot watch %s: %v",
67-
path, addErr,
68-
)
64+
unwatched++
6965
} else {
70-
count++
66+
watched++
7167
}
7268
}
7369
return nil
7470
})
75-
return count, err
71+
return watched, unwatched, err
7672
}
7773

7874
// Start begins processing file events in a goroutine.

internal/sync/watcher_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func startTestWatcherNoCleanup(
2424
if err != nil {
2525
t.Fatalf("NewWatcher: %v", err)
2626
}
27-
if _, err := w.WatchRecursive(dir); err != nil {
27+
if _, _, err := w.WatchRecursive(dir); err != nil {
2828
t.Fatalf("WatchRecursive: %v", err)
2929
}
3030
w.Start()

0 commit comments

Comments
 (0)