{"record":{"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":1105,"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":1087,"sourceCodeEnd":1123,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/pkg/api/api_impl.go#L1087-L1123","documentation":"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.","triggerScenarios":"Calling ctx.watch() twice without disposing the context in between.","commonSituations":"Multiple code paths that each call watch (e.g., initialization and config-change handler), or calling watch in a loop.","solutions":["Call watch() only once per context lifecycle","Create separate contexts for separate watch configurations","Dispose and recreate the context if you need to restart watch mode with different options"],"exampleFix":"// before\nawait ctx.watch({})\nawait ctx.watch({ delay: 500 })\n// after\nawait ctx.dispose()\nconst ctx2 = await esbuild.context(buildOptions)\nawait ctx2.watch({ delay: 500 })","handlingStrategy":"validation","validationCode":"class ManagedContext {\n  constructor(ctx) { this.ctx = ctx; this.watching = false }\n  async watch(opts) {\n    if (this.watching) throw new Error('Watch already started')\n    this.watching = true\n    return this.ctx.watch(opts)\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call watch() exactly once per context from a single initialization point","Guard against multiple initialization paths that could each call watch","Use a wrapper class to track whether watch has already been called"],"tags":["esbuild","context","watch","lifecycle"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}