evanw/esbuild · error
Cannot watch a disposed context
Error message
Cannot watch a disposed context
What it means
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.
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
Example fix
// before
await ctx.dispose()
await ctx.watch({})
// after
await ctx.dispose()
const ctx2 = await esbuild.context(buildOptions)
await ctx2.watch({}) Defensive patterns
Strategy: try-catch
Validate before calling
class ContextManager {
constructor(ctx) { this.ctx = ctx; this.disposed = false }
async watch(opts) {
if (this.disposed) throw new Error('Context already disposed')
return this.ctx.watch(opts)
}
async dispose() {
if (this.disposed) return
this.disposed = true
await this.ctx.dispose()
}
} Try / catch
try {
await ctx.watch(options)
} catch (e) {
if (e.message.includes('disposed context')) {
// recreate context and retry
ctx = await esbuild.context(buildOptions)
await ctx.watch(options)
} else {
throw e
}
} Prevention
- 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
When it happens
Trigger: Calling await ctx.dispose() and then await ctx.watch() on the same context object without creating a new one.
Common situations: Lifecycle management bugs in test teardown, hot-reload setups, or config-change handlers where a context is disposed and then accidentally reused.
Related errors
- Watch mode has already been enabled
- Cannot serve a disposed context
- Serve mode has already been enabled
- Cannot call "resolve" before plugin setup has completed
- Cannot compute relative path from
AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09).
Data as JSON: /api/errors/57c91f34ede29fa0.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/api/api_impl.go:1100
return *build
}
// Otherwise, fall back to rebuilding
ctx.mutex.Unlock()
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),
}View on GitHub (pinned to f6058f8364)