{"record":{"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":1100,"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":1082,"sourceCodeEnd":1118,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/pkg/api/api_impl.go#L1082-L1118","documentation":"This error fires when Watch() is called on an esbuild context that has already been disposed via Dispose(). The context tracks a didDispose flag and refuses to start a watcher on a disposed context since all internal state has been torn down.","triggerScenarios":"Calling await ctx.dispose() and then await ctx.watch() on the same context object without creating a new one.","commonSituations":"Lifecycle management bugs in test teardown, hot-reload setups, or config-change handlers where a context is disposed and then accidentally reused.","solutions":["Create a new context with esbuild.context(options) before calling watch after a dispose","Ensure your lifecycle ordering calls watch before dispose","Track your own disposed flag to guard against accidental reuse"],"exampleFix":"// before\nawait ctx.dispose()\nawait ctx.watch({})\n// after\nawait ctx.dispose()\nconst ctx2 = await esbuild.context(buildOptions)\nawait ctx2.watch({})","handlingStrategy":"try-catch","validationCode":"class ContextManager {\n  constructor(ctx) { this.ctx = ctx; this.disposed = false }\n  async watch(opts) {\n    if (this.disposed) throw new Error('Context already disposed')\n    return this.ctx.watch(opts)\n  }\n  async dispose() {\n    if (this.disposed) return\n    this.disposed = true\n    await this.ctx.dispose()\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await ctx.watch(options)\n} catch (e) {\n  if (e.message.includes('disposed context')) {\n    // recreate context and retry\n    ctx = await esbuild.context(buildOptions)\n    await ctx.watch(options)\n  } else {\n    throw e\n  }\n}","preventionTips":["Track context lifecycle state yourself since didDispose is not exposed","Set the context reference to null after dispose to fail fast","Never reuse a disposed context — always create a new one"],"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"}