evanw/esbuild · error
Serve mode has already been enabled
Error message
Serve mode has already been enabled
What it means
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.
Source
Thrown at pkg/api/serve_other.go:741
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)
}
}
// Validate the "fallback" path
if serveOptions.Fallback != "" {View on GitHub (pinned to 6ff1d8b0d8)
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; }
Example fix
// before
await ctx.Serve({ port: 8000 });
await ctx.Serve({ port: 8001 }); // -> Serve mode has already been enabled
// after
await ctx.Serve({ port: 8000 });
// to change: recreate the context
ctx.Dispose();
ctx = await api.Context(opts);
await ctx.Serve({ port: 8001 }); Defensive patterns
Strategy: validation
Validate before calling
let serving = false;
function startServe(ctx, opts) { if (serving) throw new Error('already serving'); serving = true; return ctx.serve(opts); } Type guard
function isAlreadyServingError(e) { return e?.message === 'Serve mode has already been enabled'; } Try / catch
try { await ctx.Serve(opts); } catch (e) { if (isAlreadyServingError(e)) return; throw e; } Prevention
- Call Serve() once per context.
- Track serving state with a flag.
- To change serve options, recreate the context.
- Centralize Serve() invocation in one module.
When it happens
Trigger: Calling ctx.Serve(opts) twice on the same context without disposing in between. The second call observes ctx.handler != nil and returns the error.
Common situations: 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.
Related errors
- Watch mode has already been enabled
- Cannot serve a disposed context
- The service was stopped
- Cannot watch a disposed context
- Must specify both key and certificate for HTTPS
AI-assisted analysis of evanw/esbuild@6ff1d8b0d8 (2026-08-03).
Data as JSON: /data/errors/2d17fdc7b9941ec0.json.
Report an issue: GitHub.