wavetermdev/waveterm · critical
internal server error: %v
Error message
internal server error: %v
What it means
httpHandlers.handleRender (tsunami/engine/serverhandlers.go:105) recovers any panic in the render handler via util.PanicHandler and returns HTTP 500 with 'internal server error: <panic>'. This is a crash of the VDom render pipeline, not a client error.
Source
Thrown at tsunami/engine/serverhandlers.go:105
mux.HandleFunc("/api/terminput", h.handleTermInput)
mux.HandleFunc("/dyn/", h.handleDynContent)
// Add handler for static files at /static/ path
if opts.StaticFS != nil {
mux.HandleFunc("/static/", h.handleStaticPathFiles(opts.StaticFS))
}
// Add fallback handler for embedded static files in production mode
if opts.AssetsFS != nil {
mux.HandleFunc("/", h.handleStaticFiles(opts.AssetsFS))
}
}
func (h *httpHandlers) handleRender(w http.ResponseWriter, r *http.Request) {
defer func() {
panicErr := util.PanicHandler("handleRender", recover())
if panicErr != nil {
http.Error(w, fmt.Sprintf("internal server error: %v", panicErr), http.StatusInternalServerError)
}
}()
setNoCacheHeaders(w)
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("failed to read request body: %v", err), http.StatusBadRequest)
return
}
var feUpdate rpctypes.VDomFrontendUpdate
if err := json.Unmarshal(body, &feUpdate); err != nil {View on GitHub (pinned to a4447c1563)
Solutions
- Check server logs for the PanicHandler output — it contains the panic message and stack trace identifying the failing code path.
- Reproduce with the specific VDomFrontendUpdate payload that triggered the crash and fix the nil/unsafe access in the render handler.
- Add nil/len guards around model or props access in the render path.
- Retry the render request after the fix; each request runs in its own goroutine so the server itself survives.
Example fix
// before
node := renderTree[update.NodeId]
applyUpdate(node, update) // panics when node is nil
// after
node, ok := renderTree[update.NodeId]
if !ok {
log.Printf("unknown node %q", update.NodeId)
return
}
applyUpdate(node, update) Defensive patterns
Strategy: retry
Validate before calling
// sanity-check the update payload before posting so the renderer never sees unexpected shapes
if (feUpdate == null || typeof feUpdate !== "object") throw new Error("invalid VDomFrontendUpdate"); Type guard
function isFeUpdate(u) {
return u != null && typeof u === "object" && "NodeId" in u;
} Try / catch
const resp = await fetch(renderUrl, {method: "POST", body: JSON.stringify(feUpdate)});
if (resp.status === 500) {
const msg = await resp.text();
if (msg.startsWith("internal server error")) {
return retryWithBackoff(() => postRender(feUpdate)); // server survived; transient panic may pass on retry
}
throw new Error(msg);
} Prevention
- Check server logs for the PanicHandler stack trace after any 500 from render.
- Guard render-path code against nil nodes/props from client-supplied updates.
- Add regression tests reproducing panics from edge-case update payloads.
When it happens
Trigger: A panic occurs anywhere inside handleRender after the deferred recover is installed — e.g. nil dereference in the render model, panicking renderer plugin, or index-out-of-range while processing a VDomFrontendUpdate.
Common situations: Malformed but structurally-valid JSON updates driving the renderer into an unexpected state, race conditions in engine state accessed by concurrent render requests, or a newly added render component that panics on nil props.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- recErr.Error()
- wcloud endpoint not set
- wcloud ping endpoint not set
- failed to copy from file: %v
- failed to copy from reader: %v
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/42ab1d16166917eb.
Report an issue: GitHub.