From 24757f5031b34fc91a1f80fea921cf8edb94f2cc Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 3 Dec 2025 20:52:01 -0800 Subject: [PATCH] Add polling fallback for Docker file watcher reliability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File watchers (inotify) don't work reliably with Docker bind mounts, so add 5-second polling as a fallback to ensure task updates are detected in containerized environments. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/aggregator/index.ts | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/aggregator/index.ts b/src/aggregator/index.ts index 26c139f..115ab69 100644 --- a/src/aggregator/index.ts +++ b/src/aggregator/index.ts @@ -157,6 +157,58 @@ export class BacklogAggregator { // Set up periodic rescan for new projects setInterval(() => this.scanForProjects(), 60000); // Every minute + + // Set up periodic task polling as fallback for Docker environments + // where inotify may not work reliably across bind mounts + setInterval(() => this.pollAllTasks(), 5000); // Every 5 seconds + } + + private async pollAllTasks(): Promise { + let hasChanges = false; + const previousTaskCount = this.tasks.size; + + for (const [projectPath] of this.projects) { + const project = this.projects.get(projectPath); + if (!project) continue; + + const tasksPath = join(projectPath, "backlog", "tasks"); + try { + const entries = await readdir(tasksPath, { withFileTypes: true }); + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) continue; + + const taskPath = join(tasksPath, entry.name); + try { + const content = await readFile(taskPath, "utf-8"); + const task = parseTask(content, taskPath); + if (task) { + const key = `${projectPath}:${task.id}`; + const existing = this.tasks.get(key); + + // Check if task is new or changed + if (!existing || existing.rawContent !== task.rawContent || existing.status !== task.status) { + const aggregatedTask: AggregatedTask = { + ...task, + projectName: project.name, + projectColor: project.color, + projectPath: projectPath, + }; + this.tasks.set(key, aggregatedTask); + hasChanges = true; + } + } + } catch { + // File read error + } + } + } catch { + // Directory read error + } + } + + if (hasChanges || this.tasks.size !== previousTaskCount) { + this.broadcastUpdate(); + } } async stop(): Promise {