evanw/esbuild · error
Cannot serve a disposed context
Error message
Cannot serve a disposed context
What it means
Returned by internalContext.Serve (pkg/api/serve_other.go) when Serve() is called on a context whose didDispose flag is already set. Like Watch, a disposed esbuild context rejects all further operations; the serve HTTP server cannot be started on a dead context. The guard at the top of Serve fails fast.
Source
Thrown at pkg/api/serve_other.go:736
return []byte(html.String())
}
// This is used to make error messages platform-independent
func prettyPrintPath(fs fs.FS, path string) string {
if relPath, ok := fs.Rel(fs.Cwd(), path); ok {
return strings.ReplaceAll(relPath, "\\", "/")
}
return path
}
func (ctx *internalContext) Serve(serveOptions ServeOptions) (ServeResult, error) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
// Ignore disposed contexts
if ctx.didDispose {
return ServeResult{}, errors.New("Cannot serve a disposed context")
}
// Don't allow starting serve mode multiple times
if ctx.handler != nil {
return ServeResult{}, errors.New("Serve mode has already been enabled")
}
// Don't allow starting serve mode multiple times
if (serveOptions.Keyfile != "") != (serveOptions.Certfile != "") {
return ServeResult{}, errors.New("Must specify both key and certificate for HTTPS")
}
// Validate the "servedir" path
if serveOptions.Servedir != "" {
if absPath, ok := ctx.realFS.Abs(serveOptions.Servedir); ok {
serveOptions.Servedir = absPath
} else {
return ServeResult{}, fmt.Errorf("Invalid serve path: %s", serveOptions.Servedir)View on GitHub (pinned to 6ff1d8b0d8)
Solutions
- After Dispose(), create a new context via api.Context(options) and call Serve() on it.
- Null out references to the context when you dispose it so stale calls are caught earlier.
- Reorder shutdown so Serve() is never invoked post-dispose.
- Track context lifetime in a wrapper object and reject operations after disposal.
Example fix
// before
ctx.Dispose();
await ctx.Serve({ port: 8000 }); // -> Cannot serve a disposed context
// after
ctx.Dispose();
ctx = await api.Context(opts);
await ctx.Serve({ port: 8000 }); Defensive patterns
Strategy: validation
Validate before calling
let disposed = false;
function safeServe(ctx, opts) { if (disposed) throw new Error('context disposed'); return ctx.serve(opts); }
// set disposed = true after dispose Type guard
function isDisposedServeError(e) { return e?.message === 'Cannot serve a disposed context'; } Try / catch
try { return await ctx.Serve(opts); } catch (e) { if (isDisposedServeError(e)) { ctx = await api.Context(o); return ctx.Serve(opts); } throw e; } Prevention
- Never call Serve() after Dispose() on the same context.
- Null out context references on dispose to surface stale calls earlier.
- Recreate the context to restart serving.
- Centralize context lifecycle management.
When it happens
Trigger: Calling ctx.Serve(opts) after ctx.Dispose() has run on the same context. The didDispose check at serve_other.go:735 triggers the error.
Common situations: Restarting a dev server by disposing the esbuild context and then calling Serve() on the stale handle; a teardown hook disposing the context before an async Serve() call lands; refactoring that leaves a dangling reference to an old context.
Related errors
- Cannot watch a disposed context
- Serve mode has already been enabled
- The service was stopped
- Watch mode has already been enabled
- Must specify both key and certificate for HTTPS
AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03).
Data as JSON: /data/errors/2b78ce447b43b4b1.json.
Report an issue: GitHub.