{"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":741,"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":723,"sourceCodeEnd":759,"githubUrl":"https://github.com/evanw/esbuild/blob/6ff1d8b0d8c134e867a397eef39702a223ebef9e/pkg/api/serve_other.go#L723-L759","documentation":"Returned by internalContext.Serve when ctx.handler is already non-nil, meaning a serve HTTP server is already running on this context. esbuild permits at most one serve instance per context; calling Serve() again is a programming error. The guard prevents binding a second listener / conflicting request handlers.","triggerScenarios":"Calling ctx.Serve(opts) twice on the same context without disposing in between. The second call observes ctx.handler != nil and returns the error.","commonSituations":"A dev server that calls Serve() on every reload without checking; two modules both trying to start serving on a shared context; port-change logic that re-invokes Serve instead of recreating the context.","solutions":["Call Serve() once per context; track whether serving has started.","To change serve options (port, servedir, TLS), dispose the context, create a new one, and Serve() again.","Centralize the Serve() call so only one code path can invoke it.","Guard with a flag: if (!serving) { ctx.Serve(...); serving = true; }"],"exampleFix":"// before\nawait ctx.Serve({ port: 8000 });\nawait ctx.Serve({ port: 8001 }); // -> Serve mode has already been enabled\n\n// after\nawait ctx.Serve({ port: 8000 });\n// to change: recreate the context\nctx.Dispose();\nctx = await api.Context(opts);\nawait ctx.Serve({ port: 8001 });","handlingStrategy":"validation","validationCode":"let serving = false;\nfunction startServe(ctx, opts) { if (serving) throw new Error('already serving'); serving = true; return ctx.serve(opts); }","typeGuard":"function isAlreadyServingError(e) { return e?.message === 'Serve mode has already been enabled'; }","tryCatchPattern":"try { await ctx.Serve(opts); } catch (e) { if (isAlreadyServingError(e)) return; throw e; }","preventionTips":["Call Serve() once per context.","Track serving state with a flag.","To change serve options, recreate the context.","Centralize Serve() invocation in one module."],"tags":["esbuild","serve","lifecycle","duplicate"],"analyzedSha":"6ff1d8b0d8c134e867a397eef39702a223ebef9e","analyzedAt":"2026-08-03T19:42:38.433Z","schemaVersion":2}