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.

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)

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.