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

  1. After Dispose(), create a new context via api.Context(options) and call Serve() on it.
  2. Null out references to the context when you dispose it so stale calls are caught earlier.
  3. Reorder shutdown so Serve() is never invoked post-dispose.
  4. 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

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


AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03). Data as JSON: /data/errors/2b78ce447b43b4b1.json. Report an issue: GitHub.