{"id":"e93055fc54838f28","repo":"evanw/esbuild","slug":"watch-mode-has-already-been-enabled","errorCode":null,"errorMessage":"Watch mode has already been enabled","messagePattern":"Watch mode has already been enabled","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/api/api_impl.go","lineNumber":1084,"sourceCode":"\treturn ctx.Rebuild()\n}\n\nfunc (ctx *internalContext) Rebuild() BuildResult {\n\treturn ctx.rebuild().result\n}\n\nfunc (ctx *internalContext) Watch(options WatchOptions) error {\n\tctx.mutex.Lock()\n\tdefer ctx.mutex.Unlock()\n\n\t// Ignore disposed contexts\n\tif ctx.didDispose {\n\t\treturn errors.New(\"Cannot watch a disposed context\")\n\t}\n\n\t// Don't allow starting watch mode multiple times\n\tif ctx.watcher != nil {\n\t\treturn errors.New(\"Watch mode has already been enabled\")\n\t}\n\n\tlogLevel := ctx.args.logOptions.LogLevel\n\tctx.watcher = &watcher{\n\t\tfs:        ctx.realFS,\n\t\tshouldLog: logLevel == logger.LevelInfo || logLevel == logger.LevelDebug || logLevel == logger.LevelVerbose,\n\t\tuseColor:  ctx.args.logOptions.Color,\n\t\tpathStyle: ctx.args.logOptions.PathStyle,\n\t\trebuild: func() fs.WatchData {\n\t\t\treturn ctx.rebuild().watchData\n\t\t},\n\t\tdelayInMS: time.Duration(options.Delay),\n\t}\n\n\t// All subsequent builds will be watch mode builds\n\tctx.args.options.WatchMode = true\n\n\t// Start the file watcher goroutine","sourceCodeStart":1066,"sourceCodeEnd":1102,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/pkg/api/api_impl.go#L1066-L1102","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Call Watch() exactly once per context; track a boolean so you don't re-invoke it.","If you need to change watch behavior, dispose the context and create a new one before calling Watch() again.","Move the Watch() call into context initialization so it cannot be reached twice.","Guard the call: if (!watching) { ctx.Watch({}); watching = true; }"],"exampleFix":"// before\nctx.Watch({});\nctx.Watch({}); // -> Watch mode has already been enabled\n\n// after\nif (!watching) { ctx.Watch({}); watching = true; }","handlingStrategy":"validation","validationCode":"let watching = false;\nfunction startWatch(ctx, opts) { if (watching) return; ctx.watch(opts); watching = true; }","typeGuard":"function isAlreadyWatchingError(e) { return e?.message === 'Watch mode has already been enabled'; }","tryCatchPattern":"try { ctx.Watch(opts); } catch (e) { if (isAlreadyWatchingError(e)) return; throw e; }","preventionTips":["Call Watch() exactly once per context.","Use a boolean guard to prevent duplicate calls.","To change watch config, recreate the context.","Put Watch() in context initialization code only."],"tags":["esbuild","watch","lifecycle","duplicate"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}