* Start watching the team memory directory for changes. * * Uses `fs.watch({recursive: true})` on the directory (not chokidar). * chokidar 4+ dropped fsevents, and Bun's `fs.watch` fallback uses kqueue, * which requires one open fd per watched file — with 500+ team memory files * that's 500+ pe
(teamDir: string)
| 165 | * delete team memory files to recover, they restart with auth. |
| 166 | */ |
| 167 | async function startFileWatcher(teamDir: string): Promise<void> { |
| 168 | if (watcherStarted) { |
| 169 | return |
| 170 | } |
| 171 | watcherStarted = true |
| 172 | |
| 173 | try { |
| 174 | // pullTeamMemory returns early without creating the dir for fresh repos |
| 175 | // with no server content (index.ts isEmpty path). mkdir with |
| 176 | // recursive:true is idempotent — no existence check needed. |
| 177 | await mkdir(teamDir, { recursive: true }) |
| 178 | |
| 179 | watcher = watch( |
| 180 | teamDir, |
| 181 | { persistent: true, recursive: true }, |
| 182 | (_eventType, filename) => { |
| 183 | if (filename === null) { |
| 184 | schedulePush() |
| 185 | return |
| 186 | } |
| 187 | if (pushSuppressedReason !== null) { |
| 188 | // Suppression is only cleared by unlink (recovery action for |
| 189 | // too-many-entries). fs.watch doesn't distinguish unlink from |
| 190 | // add/write — stat to disambiguate. ENOENT → file gone → clear. |
| 191 | void stat(join(teamDir, filename)).catch( |
| 192 | (err: NodeJS.ErrnoException) => { |
| 193 | if (err.code !== 'ENOENT') return |
| 194 | if (pushSuppressedReason !== null) { |
| 195 | logForDebugging( |
| 196 | `team-memory-watcher: unlink cleared suppression (was: ${pushSuppressedReason})`, |
| 197 | { level: 'info' }, |
| 198 | ) |
| 199 | pushSuppressedReason = null |
| 200 | } |
| 201 | schedulePush() |
| 202 | }, |
| 203 | ) |
| 204 | return |
| 205 | } |
| 206 | schedulePush() |
| 207 | }, |
| 208 | ) |
| 209 | watcher.on('error', err => { |
| 210 | logForDebugging( |
| 211 | `team-memory-watcher: fs.watch error: ${errorMessage(err)}`, |
| 212 | { level: 'warn' }, |
| 213 | ) |
| 214 | }) |
| 215 | logForDebugging(`team-memory-watcher: watching ${teamDir}`, { |
| 216 | level: 'debug', |
| 217 | }) |
| 218 | } catch (err) { |
| 219 | // fs.watch throws synchronously on ENOENT (race: dir deleted between |
| 220 | // mkdir and watch) or EACCES. watcherStarted is already true above, |
| 221 | // so notifyTeamMemoryWrite's explicit schedulePush path still works. |
| 222 | logForDebugging( |
| 223 | `team-memory-watcher: failed to watch ${teamDir}: ${errorMessage(err)}`, |
| 224 | { level: 'warn' }, |
no test coverage detected