abhigyanpatwari/GitNexus · error

Watch refresh is already running

Error message

Watch refresh is already running

What it means

Thrown by WatchQueue.runInitial in watch-queue.ts when the initial `analyze --watch` refresh is requested while another refresh batch is already active. runInitial is meant to be the first, exclusive refresh; the queue serializes all refreshes, so a concurrent one is a programming error by the caller, not a recoverable condition.

Solutions

  1. Call runInitial() exactly once per WatchQueue, before any events are scheduled, and await it before proceeding.
  2. Guard the call with the queue's own state: only invoke when no refresh is active (or track a local `started` flag).
  3. For subsequent refreshes, rely on the queue's event scheduling (`schedule()` / pending work) instead of calling runInitial again.
  4. If you need to re-run after a failure, close and recreate the WatchQueue rather than re-calling runInitial.

Example fix

// before
await queue.runInitial();
watcher.on('change', () => queue.runInitial()); // throws on overlap

// after
await queue.runInitial(); // once, at startup
watcher.on('change', () => queue.enqueue(changedPath)); // queue serializes the rest
Defensive patterns

Strategy: try-catch

Validate before calling

if (queue.hasActiveRefresh?.()) {
  throw new Error('skip: initial refresh already in progress');
}
// or track locally: let initialDone = false; only call runInitial when !initialDone

Try / catch

try {
  await queue.runInitial();
  initialDone = true;
} catch (e) {
  if (e.message === 'Watch refresh is already running') {
    // another refresh owns the queue; wait for idle instead of retrying
    await queue.waitForIdle();
  } else throw e;
}

Prevention

When it happens

Trigger: Calling watchQueue.runInitial() while `this.active` is set — e.g. calling runInitial twice, or calling it after event-driven refreshes have already started, or concurrently from two async tasks before the first await resolves the batch.

Common situations: Custom tooling wrapping the watch mode that re-invokes runInitial on a timer or file event; race conditions when starting the watcher and queueing events at nearly the same time; tests that construct a shared queue and call runInitial from multiple cases.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-09-01). Data as JSON: /api/errors/de5196a96776d161. Report an issue: GitHub.

Appendix: source

Thrown at gitnexus/src/cli/watch-queue.ts:68

      this.pending.add(filePath);
    } else {
      this.overflowed = true;
      if (priority) {
        const evictable = [...this.pending].find(
          (pendingPath) => this.options.isPriorityPath?.(pendingPath) !== true,
        );
        if (evictable !== undefined) {
          this.pending.delete(evictable);
          this.pending.add(filePath);
        }
      }
    }
  }

  /** Run the initial refresh while still queueing events that arrive during it. */
  async runInitial(): Promise<void> {
    if (this.closed) return;
    if (this.active !== undefined) throw new Error('Watch refresh is already running');
    try {
      await this.runBatch([], true);
    } finally {
      this.initialPending = false;
      if (!this.closed && this.hasPendingWork()) this.schedule();
      else this.resolveIdleWaiters();
    }
  }

  async waitForIdle(): Promise<void> {
    if (this.isIdle()) return;
    await new Promise<void>((resolve) => this.idleWaiters.add(resolve));
  }

  async close(): Promise<void> {
    this.closed = true;
    if (this.timer !== undefined) clearTimeout(this.timer);
    this.timer = undefined;

View on GitHub (pinned to 52924ef12c)