evanw/esbuild · error

The service was stopped

Error message

The service was stopped

What it means

Returned from the OnEnd plugin callback registered in cmd/esbuild/service.go:746. The Go-side bridge calls service.sendRequest to forward the on-end event to the (separate, JS-driven) esbuild service and type-asserts the reply to map[string]interface{}. If the assertion fails it means sendRequest did not receive a well-formed response — the service process has been disposed, crashed, or its packet stream is broken — so the callback returns errors.New("The service was stopped").

Solutions

  1. Treat this as a terminal condition for the context: do not issue further build calls on it; create a new context instead.
  2. Ensure dispose() is not called concurrently with an in-flight build, or expect OnEnd to surface this error and handle it gracefully.
  3. If the service is crashing unexpectedly, capture stderr/logs from the esbuild subprocess to find the root cause (OOM, panic).

Example fix

// before (ignoring the stopped error, then reusing the dead context)
result := ctx.Rebuild()
// later: ctx.Dispose() fires OnEnd with "The service was stopped"
ctx.Rebuild() // hangs/fails

// after (treat stopped as terminal, recreate context)
result, err := ctx.Rebuild()
if err != nil && err.Error() == "The service was stopped" {
  ctx.Dispose()
  ctx = newContext() // fresh esbuild context
}
Defensive patterns

Strategy: try-catch

Validate before calling

// No pre-call validation can fully prevent this; instead, ensure the service is alive
func serviceAlive(svc *serviceType) bool {
  svc.mutex.Lock(); defer svc.mutex.Unlock()
  return !svc.isDisposed // (conceptual) check the lifecycle flag before issuing requests
}

Try / catch

result, err := ctx.Rebuild()
if err != nil && err.Error() == "The service was stopped" {
  ctx.Dispose()
  ctx, _ = api.Context(opts) // recreate; the old context is terminal
  result, err = ctx.Rebuild()
}

Prevention

When it happens

Trigger: Calling dispose() on a build context (or the underlying esbuild service process exiting/crashing) while an OnEnd callback is mid-flight. The reply channel either yields nil or a non-map value, so the type assertion `.(map[string]interface{})` returns ok=false.

Common situations: A long-running watch/rebuild context is disposed while a build is in progress; the service subprocess is killed by the OS or OOM; or a host application tears down esbuild on shutdown while plugin callbacks are still running.

Related errors


AI-assisted analysis of evanw/esbuild@f6058f8364 (2026-08-09). Data as JSON: /api/errors/602737caabe28e69. Report an issue: GitHub.

Appendix: source

Thrown at cmd/esbuild/service.go:746

					//
					// This is especially important if "write" is false since otherwise
					// we'd unnecessarily send the entire contents of all output files!
					//
					//          "If a tree falls in a forest and no one is
					//           around to hear it, does it make a sound?"
					//
					activeBuild.mutex.Lock()
					isWithinRebuild := activeBuild.withinRebuildCount > 0
					activeBuild.mutex.Unlock()
					if !hasOnEndCallbacks && !isWithinRebuild && !writeToStdout {
						return api.OnEndResult{}, nil
					}
					request := resultToResponse(*result)
					request["command"] = "on-end"
					request["key"] = key
					response, ok := service.sendRequest(request).(map[string]interface{})
					if !ok {
						return api.OnEndResult{}, errors.New("The service was stopped")
					}
					var errors []api.Message
					var warnings []api.Message
					if value, ok := response["errors"].([]interface{}); ok {
						errors = decodeMessages(value)
					}
					if value, ok := response["warnings"].([]interface{}); ok {
						warnings = decodeMessages(value)
					}
					return api.OnEndResult{
						Errors:   errors,
						Warnings: warnings,
					}, nil
				})
			},
		})

		ctx, err := api.Context(options)

View on GitHub (pinned to f6058f8364)