evanw/esbuild · error

Cannot serve a disposed context

Error message

Cannot serve a disposed context

What it means

This error fires when Serve() is called on an esbuild context that has already been disposed. The context checks the didDispose flag and refuses to start a serve handler because all internal state has been torn down.

Solutions

  1. Create a fresh context with esbuild.context(options) before calling serve
  2. Coordinate serve and dispose ordering in your server shutdown handler
  3. Null out the context reference after dispose to catch stale usage early

Example fix

// before
await ctx.dispose()
await ctx.serve({ servedir: '.' })
// after
await ctx.dispose()
const ctx2 = await esbuild.context(buildOptions)
await ctx2.serve({ servedir: '.' })
Defensive patterns

Strategy: try-catch

Validate before calling

class ServeManager {
  constructor(ctx) { this.ctx = ctx; this.disposed = false }
  async serve(opts) {
    if (this.disposed) throw new Error('Context already disposed — create a new context')
    return this.ctx.serve(opts)
  }
  async dispose() {
    if (this.disposed) return
    this.disposed = true
    await this.ctx.dispose()
  }
}

Try / catch

try {
  await ctx.serve(serveOptions)
} catch (e) {
  if (e.message.includes('disposed context')) {
    ctx = await esbuild.context(buildOptions)
    await ctx.serve(serveOptions)
  } else {
    throw e
  }
}

Prevention

When it happens

Trigger: Calling await ctx.dispose() and then await ctx.serve(options) on the same context object.

Common situations: Dev server lifecycle bugs where a context is disposed during hot-reload or config change and then serve is called again on the stale reference.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/2b78ce447b43b4b1. Report an issue: GitHub.

Appendix: source

Thrown at pkg/api/serve_other.go:753

	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 f6058f8364)