evanw/esbuild · error

Watch mode has already been enabled

Error message

Watch mode has already been enabled

What it means

esbuild contexts support only one active watcher at a time. This error fires when Watch() is called on a context that already has a non-nil watcher field, indicating watch mode is already running.

Solutions

  1. Call watch() only once per context lifecycle
  2. Create separate contexts for separate watch configurations
  3. Dispose and recreate the context if you need to restart watch mode with different options

Example fix

// before
await ctx.watch({})
await ctx.watch({ delay: 500 })
// after
await ctx.dispose()
const ctx2 = await esbuild.context(buildOptions)
await ctx2.watch({ delay: 500 })
Defensive patterns

Strategy: validation

Validate before calling

class ManagedContext {
  constructor(ctx) { this.ctx = ctx; this.watching = false }
  async watch(opts) {
    if (this.watching) throw new Error('Watch already started')
    this.watching = true
    return this.ctx.watch(opts)
  }
}

Prevention

When it happens

Trigger: Calling ctx.watch() twice without disposing the context in between.

Common situations: Multiple code paths that each call watch (e.g., initialization and config-change handler), or calling watch in a loop.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/e93055fc54838f28. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/api_impl.go:1105

	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 f6058f8364)