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
- Call runInitial() exactly once per WatchQueue, before any events are scheduled, and await it before proceeding.
- Guard the call with the queue's own state: only invoke when no refresh is active (or track a local `started` flag).
- For subsequent refreshes, rely on the queue's event scheduling (`schedule()` / pending work) instead of calling runInitial again.
- 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
- Call runInitial exactly once at watcher startup and await it before wiring event handlers.
- Route all later refresh requests through the queue's enqueue/schedule API, never runInitial.
- Await the previous runInitial before any code path that could trigger another.
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
- ${flag} must be a positive integer
- ${flag} must not exceed ${maximum}
- analyze --watch does not support ${unsupported.map(([name])
- analyze --watch does not support ${unsupported.map(([name])
- Ignore controls remain invalid; fix them before indexing mor
AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-09-01).
Data as JSON: /api/errors/de5196a96776d161.
Report an issue: GitHub.