{"record":{"id":"2b78ce447b43b4b1","repo":"evanw/esbuild","slug":"cannot-serve-a-disposed-context","errorCode":null,"errorMessage":"Cannot serve a disposed context","messagePattern":"Cannot serve a disposed context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/api/serve_other.go","lineNumber":753,"sourceCode":"\n\treturn []byte(html.String())\n}\n\n// This is used to make error messages platform-independent\nfunc prettyPrintPath(fs fs.FS, path string) string {\n\tif relPath, ok := fs.Rel(fs.Cwd(), path); ok {\n\t\treturn strings.ReplaceAll(relPath, \"\\\\\", \"/\")\n\t}\n\treturn path\n}\n\nfunc (ctx *internalContext) Serve(serveOptions ServeOptions) (ServeResult, error) {\n\tctx.mutex.Lock()\n\tdefer ctx.mutex.Unlock()\n\n\t// Ignore disposed contexts\n\tif ctx.didDispose {\n\t\treturn ServeResult{}, errors.New(\"Cannot serve a disposed context\")\n\t}\n\n\t// Don't allow starting serve mode multiple times\n\tif ctx.handler != nil {\n\t\treturn ServeResult{}, errors.New(\"Serve mode has already been enabled\")\n\t}\n\n\t// Don't allow starting serve mode multiple times\n\tif (serveOptions.Keyfile != \"\") != (serveOptions.Certfile != \"\") {\n\t\treturn ServeResult{}, errors.New(\"Must specify both key and certificate for HTTPS\")\n\t}\n\n\t// Validate the \"servedir\" path\n\tif serveOptions.Servedir != \"\" {\n\t\tif absPath, ok := ctx.realFS.Abs(serveOptions.Servedir); ok {\n\t\t\tserveOptions.Servedir = absPath\n\t\t} else {\n\t\t\treturn ServeResult{}, fmt.Errorf(\"Invalid serve path: %s\", serveOptions.Servedir)","sourceCodeStart":735,"sourceCodeEnd":771,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/pkg/api/serve_other.go#L735-L771","documentation":"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.","triggerScenarios":"Calling await ctx.dispose() and then await ctx.serve(options) on the same context object.","commonSituations":"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.","solutions":["Create a fresh context with esbuild.context(options) before calling serve","Coordinate serve and dispose ordering in your server shutdown handler","Null out the context reference after dispose to catch stale usage early"],"exampleFix":"// before\nawait ctx.dispose()\nawait ctx.serve({ servedir: '.' })\n// after\nawait ctx.dispose()\nconst ctx2 = await esbuild.context(buildOptions)\nawait ctx2.serve({ servedir: '.' })","handlingStrategy":"try-catch","validationCode":"class ServeManager {\n  constructor(ctx) { this.ctx = ctx; this.disposed = false }\n  async serve(opts) {\n    if (this.disposed) throw new Error('Context already disposed — create a new context')\n    return this.ctx.serve(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.serve(serveOptions)\n} catch (e) {\n  if (e.message.includes('disposed context')) {\n    ctx = await esbuild.context(buildOptions)\n    await ctx.serve(serveOptions)\n  } else {\n    throw e\n  }\n}","preventionTips":["Coordinate serve and dispose calls in your server lifecycle manager","Null out context references after dispose to catch stale usage","Never assume a context is still valid after a dispose call"],"tags":["esbuild","context","serve","lifecycle"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}