{"id":"57c91f34ede29fa0","repo":"evanw/esbuild","slug":"cannot-watch-a-disposed-context","errorCode":null,"errorMessage":"Cannot watch a disposed context","messagePattern":"Cannot watch a disposed context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/api/api_impl.go","lineNumber":1079,"sourceCode":"\t\treturn *build\n\t}\n\n\t// Otherwise, fall back to rebuilding\n\tctx.mutex.Unlock()\n\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}","sourceCodeStart":1061,"sourceCodeEnd":1097,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/pkg/api/api_impl.go#L1061-L1097","documentation":"Returned by internalContext.Watch (pkg/api/api_impl.go) when you call Watch() on a context whose didDispose flag is already true. esbuild contexts are single-use after disposal — once Dispose() runs, all further operations on that context are rejected. The guard is intentional so that a disposed context fails fast rather than silently no-op'ing.","triggerScenarios":"Calling ctx.Watch(opts) on an esbuild context after ctx.Dispose() has already been invoked on it. The mutex-guarded didDispose check at the top of Watch triggers the error.","commonSituations":"Restarting watch mode by disposing and re-Watch()'ing the same context instead of creating a new one; a cleanup handler that disposes the context and a later code path tries to (re)start watching; incorrect ordering in a dev-server teardown/restart flow.","solutions":["Create a fresh context with api.Context(options) and call Watch() on the new one after disposing the old.","Track whether you've disposed the context and skip/guard the Watch call accordingly.","Reorder teardown so Watch() is never called after Dispose() (stop watching first, then dispose).","Use a single long-lived context for watch mode and avoid dispose/recreate cycles."],"exampleFix":"// before\nctx.Dispose();\nctx.Watch({}); // -> Cannot watch a disposed context\n\n// after\nctx.Dispose();\nctx, _ = api.Context(opts);\nctx.Watch({});","handlingStrategy":"validation","validationCode":"let disposed = false;\nfunction safeWatch(ctx, opts) { if (disposed) throw new Error('context disposed'); ctx.watch(opts); }\n// set disposed = true after ctx.dispose()","typeGuard":"function isDisposedError(e) { return e?.message === 'Cannot watch a disposed context'; }","tryCatchPattern":"try { ctx.Watch(opts); } catch (e) { if (isDisposedError(e)) { ctx = await api.Context(opts2); ctx.Watch(opts); } else throw e; }","preventionTips":["Track context disposal in a flag and guard Watch().","Create a new context after Dispose() rather than reusing.","Stop watching before disposing.","Centralize context lifecycle in a single manager."],"tags":["esbuild","watch","lifecycle","context"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}