evanw/esbuild · error

Watch mode has already been enabled

Error message

Watch mode has already been enabled

What it means

Returned by internalContext.Watch when ctx.watcher is already non-nil, meaning watch mode is already running on this context. esbuild allows watch mode to be enabled at most once per context; calling Watch() a second time is a programming error. The check prevents duplicate file watchers and conflicting rebuild triggers.

Source

Thrown at pkg/api/api_impl.go:1084

	return ctx.Rebuild()
}

func (ctx *internalContext) Rebuild() BuildResult {
	return ctx.rebuild().result
}

func (ctx *internalContext) Watch(options WatchOptions) error {
	ctx.mutex.Lock()
	defer ctx.mutex.Unlock()

	// Ignore disposed contexts
	if ctx.didDispose {
		return errors.New("Cannot watch a disposed context")
	}

	// Don't allow starting watch mode multiple times
	if ctx.watcher != nil {
		return errors.New("Watch mode has already been enabled")
	}

	logLevel := ctx.args.logOptions.LogLevel
	ctx.watcher = &watcher{
		fs:        ctx.realFS,
		shouldLog: logLevel == logger.LevelInfo || logLevel == logger.LevelDebug || logLevel == logger.LevelVerbose,
		useColor:  ctx.args.logOptions.Color,
		pathStyle: ctx.args.logOptions.PathStyle,
		rebuild: func() fs.WatchData {
			return ctx.rebuild().watchData
		},
		delayInMS: time.Duration(options.Delay),
	}

	// All subsequent builds will be watch mode builds
	ctx.args.options.WatchMode = true

	// Start the file watcher goroutine

View on GitHub (pinned to 6ff1d8b0d8)

Solutions

  1. Call Watch() exactly once per context; track a boolean so you don't re-invoke it.
  2. If you need to change watch behavior, dispose the context and create a new one before calling Watch() again.
  3. Move the Watch() call into context initialization so it cannot be reached twice.
  4. Guard the call: if (!watching) { ctx.Watch({}); watching = true; }

Example fix

// before
ctx.Watch({});
ctx.Watch({}); // -> Watch mode has already been enabled

// after
if (!watching) { ctx.Watch({}); watching = true; }
Defensive patterns

Strategy: validation

Validate before calling

let watching = false;
function startWatch(ctx, opts) { if (watching) return; ctx.watch(opts); watching = true; }

Type guard

function isAlreadyWatchingError(e) { return e?.message === 'Watch mode has already been enabled'; }

Try / catch

try { ctx.Watch(opts); } catch (e) { if (isAlreadyWatchingError(e)) return; throw e; }

Prevention

When it happens

Trigger: Calling ctx.Watch(opts) twice on the same esbuild context without disposing in between. The second call sees ctx.watcher != nil and returns the error.

Common situations: A dev tool that calls Watch() in a reload handler without checking if it already started; conditional code that invokes Watch() in two separate branches; wrapping esbuild and restarting watch on config edit without recreating the context.

Related errors


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/e93055fc54838f28.json. Report an issue: GitHub.