{"record":{"id":"2d17fdc7b9941ec0","repo":"evanw/esbuild","slug":"serve-mode-has-already-been-enabled","errorCode":null,"errorMessage":"Serve mode has already been enabled","messagePattern":"Serve mode has already been enabled","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/api/serve_other.go","lineNumber":758,"sourceCode":"func 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)\n\t\t}\n\t}\n\n\t// Validate the \"fallback\" path\n\tif serveOptions.Fallback != \"\" {","sourceCodeStart":740,"sourceCodeEnd":776,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/pkg/api/serve_other.go#L740-L776","documentation":"esbuild contexts support only one serve handler. This error fires when Serve() is called on a context that already has a non-nil handler field, indicating serve mode is already active.","triggerScenarios":"Calling ctx.serve(...) twice on the same context without creating a new one.","commonSituations":"Dev tooling that restarts serve on config change without recreating the context, or calling serve in multiple initialization paths.","solutions":["Call serve() only once per context lifecycle","Dispose the context and create a new one to restart serving with different options","Guard with your own 'already serving' flag if multiple code paths can trigger serve"],"exampleFix":"// before\nawait ctx.serve({ port: 8000 })\nawait ctx.serve({ port: 9000 })\n// after\nawait ctx.dispose()\nconst ctx2 = await esbuild.context(buildOptions)\nawait ctx2.serve({ port: 9000 })","handlingStrategy":"validation","validationCode":"class ServeGuard {\n  constructor(ctx) { this.ctx = ctx; this.serving = false }\n  async serve(opts) {\n    if (this.serving) throw new Error('Serve already started on this context')\n    this.serving = true\n    return this.ctx.serve(opts)\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Call serve() only once per context from a single initialization point","Create a new context to restart serving with different options","Use a flag or wrapper class to prevent duplicate serve calls"],"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"}