{"record":{"id":"602737caabe28e69","repo":"evanw/esbuild","slug":"the-service-was-stopped","errorCode":null,"errorMessage":"The service was stopped","messagePattern":"The service was stopped","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/esbuild/service.go","lineNumber":746,"sourceCode":"\t\t\t\t\t//\n\t\t\t\t\t// This is especially important if \"write\" is false since otherwise\n\t\t\t\t\t// we'd unnecessarily send the entire contents of all output files!\n\t\t\t\t\t//\n\t\t\t\t\t//          \"If a tree falls in a forest and no one is\n\t\t\t\t\t//           around to hear it, does it make a sound?\"\n\t\t\t\t\t//\n\t\t\t\t\tactiveBuild.mutex.Lock()\n\t\t\t\t\tisWithinRebuild := activeBuild.withinRebuildCount > 0\n\t\t\t\t\tactiveBuild.mutex.Unlock()\n\t\t\t\t\tif !hasOnEndCallbacks && !isWithinRebuild && !writeToStdout {\n\t\t\t\t\t\treturn api.OnEndResult{}, nil\n\t\t\t\t\t}\n\t\t\t\t\trequest := resultToResponse(*result)\n\t\t\t\t\trequest[\"command\"] = \"on-end\"\n\t\t\t\t\trequest[\"key\"] = key\n\t\t\t\t\tresponse, ok := service.sendRequest(request).(map[string]interface{})\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn api.OnEndResult{}, errors.New(\"The service was stopped\")\n\t\t\t\t\t}\n\t\t\t\t\tvar errors []api.Message\n\t\t\t\t\tvar warnings []api.Message\n\t\t\t\t\tif value, ok := response[\"errors\"].([]interface{}); ok {\n\t\t\t\t\t\terrors = decodeMessages(value)\n\t\t\t\t\t}\n\t\t\t\t\tif value, ok := response[\"warnings\"].([]interface{}); ok {\n\t\t\t\t\t\twarnings = decodeMessages(value)\n\t\t\t\t\t}\n\t\t\t\t\treturn api.OnEndResult{\n\t\t\t\t\t\tErrors:   errors,\n\t\t\t\t\t\tWarnings: warnings,\n\t\t\t\t\t}, nil\n\t\t\t\t})\n\t\t\t},\n\t\t})\n\n\t\tctx, err := api.Context(options)","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/evanw/esbuild/blob/f6058f8364fe7ab91ca57a83e02577ed74c9cae4/cmd/esbuild/service.go#L728-L764","documentation":"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\").","triggerScenarios":"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.","commonSituations":"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.","solutions":["Treat this as a terminal condition for the context: do not issue further build calls on it; create a new context instead.","Ensure dispose() is not called concurrently with an in-flight build, or expect OnEnd to surface this error and handle it gracefully.","If the service is crashing unexpectedly, capture stderr/logs from the esbuild subprocess to find the root cause (OOM, panic)."],"exampleFix":"// before (ignoring the stopped error, then reusing the dead context)\nresult := ctx.Rebuild()\n// later: ctx.Dispose() fires OnEnd with \"The service was stopped\"\nctx.Rebuild() // hangs/fails\n\n// after (treat stopped as terminal, recreate context)\nresult, err := ctx.Rebuild()\nif err != nil && err.Error() == \"The service was stopped\" {\n  ctx.Dispose()\n  ctx = newContext() // fresh esbuild context\n}","handlingStrategy":"try-catch","validationCode":"// No pre-call validation can fully prevent this; instead, ensure the service is alive\nfunc serviceAlive(svc *serviceType) bool {\n  svc.mutex.Lock(); defer svc.mutex.Unlock()\n  return !svc.isDisposed // (conceptual) check the lifecycle flag before issuing requests\n}","typeGuard":null,"tryCatchPattern":"result, err := ctx.Rebuild()\nif err != nil && err.Error() == \"The service was stopped\" {\n  ctx.Dispose()\n  ctx, _ = api.Context(opts) // recreate; the old context is terminal\n  result, err = ctx.Rebuild()\n}","preventionTips":["Do not call dispose() while a build is in flight; coordinate teardown with build completion.","Treat 'The service was stopped' as terminal for that context — never reuse it.","Capture esbuild subprocess stderr to diagnose unexpected (non-dispose) stops like OOM or panics."],"tags":["esbuild","go","plugin","on-end","service-lifecycle","dispose"],"backgroundTag":null,"analyzedSha":"f6058f8364fe7ab91ca57a83e02577ed74c9cae4","analyzedAt":"2026-08-09T18:37:22.223Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}