{"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":736,"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":718,"sourceCodeEnd":754,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/pkg/api/serve_other.go#L718-L754","documentation":"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.","triggerScenarios":"Calling ctx.Serve(opts) after ctx.Dispose() has run on the same context. The didDispose check at serve_other.go:735 triggers the error.","commonSituations":"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.","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."],"exampleFix":"// before\nctx.Dispose();\nawait ctx.Serve({ port: 8000 }); // -> Cannot serve a disposed context\n\n// after\nctx.Dispose();\nctx = await api.Context(opts);\nawait ctx.Serve({ port: 8000 });","handlingStrategy":"validation","validationCode":"let disposed = false;\nfunction safeServe(ctx, opts) { if (disposed) throw new Error('context disposed'); return ctx.serve(opts); }\n// set disposed = true after dispose","typeGuard":"function isDisposedServeError(e) { return e?.message === 'Cannot serve a disposed context'; }","tryCatchPattern":"try { return await ctx.Serve(opts); } catch (e) { if (isDisposedServeError(e)) { ctx = await api.Context(o); return ctx.Serve(opts); } throw e; }","preventionTips":["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."],"tags":["esbuild","serve","lifecycle","context"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}