()
| 394 | } |
| 395 | |
| 396 | async function enable() { |
| 397 | if (stopped) return |
| 398 | if (enablePoll) { |
| 399 | clearInterval(enablePoll) |
| 400 | enablePoll = null |
| 401 | } |
| 402 | |
| 403 | const { default: chokidar } = await import('chokidar') |
| 404 | if (stopped) return |
| 405 | |
| 406 | // Acquire the per-project scheduler lock. Only the owning session runs |
| 407 | // check(). Other sessions probe periodically to take over if the owner |
| 408 | // dies. Prevents double-firing when multiple Claudes share a cwd. |
| 409 | isOwner = await tryAcquireSchedulerLock(lockOpts).catch(() => false) |
| 410 | if (stopped) { |
| 411 | if (isOwner) { |
| 412 | isOwner = false |
| 413 | void releaseSchedulerLock(lockOpts) |
| 414 | } |
| 415 | return |
| 416 | } |
| 417 | if (!isOwner) { |
| 418 | lockProbeTimer = setInterval(() => { |
| 419 | void tryAcquireSchedulerLock(lockOpts) |
| 420 | .then(owned => { |
| 421 | if (stopped) { |
| 422 | if (owned) void releaseSchedulerLock(lockOpts) |
| 423 | return |
| 424 | } |
| 425 | if (owned) { |
| 426 | isOwner = true |
| 427 | if (lockProbeTimer) { |
| 428 | clearInterval(lockProbeTimer) |
| 429 | lockProbeTimer = null |
| 430 | } |
| 431 | } |
| 432 | }) |
| 433 | .catch(e => logForDebugging(String(e), { level: 'error' })) |
| 434 | }, LOCK_PROBE_INTERVAL_MS) |
| 435 | lockProbeTimer.unref?.() |
| 436 | } |
| 437 | |
| 438 | void load(true) |
| 439 | |
| 440 | const path = getCronFilePath(dir) |
| 441 | watcher = chokidar.watch(path, { |
| 442 | persistent: false, |
| 443 | ignoreInitial: true, |
| 444 | awaitWriteFinish: { stabilityThreshold: FILE_STABILITY_MS }, |
| 445 | ignorePermissionErrors: true, |
| 446 | }) |
| 447 | watcher.on('add', () => void load(false)) |
| 448 | watcher.on('change', () => void load(false)) |
| 449 | watcher.on('unlink', () => { |
| 450 | if (!stopped) { |
| 451 | tasks = [] |
| 452 | nextFireAt.clear() |
| 453 | } |
no test coverage detected