wavetermdev/waveterm · error
failed to decode tsunami response: %w
Error message
failed to decode tsunami response: %w
What it means
This error wraps a JSON decoding failure when the AI tool callback reads the response body from a local Tsunami widget's HTTP API (GET /api/data or /api/config). The Tsunami server returned HTTP 200, but its body was not valid JSON (or was empty/truncated), so json.Decoder.Decode failed. The underlying decoder error is preserved via %w so the root cause (e.g. 'unexpected end of JSON input' or 'invalid character') is visible.
Source
Thrown at pkg/aiusechat/tools_tsunami.go:75
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request to tsunami: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("tsunami returned status %d", resp.StatusCode)
}
var result any
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode tsunami response: %w", err)
}
return result, nil
}
}
func makeTsunamiPostCallback(status *blockcontroller.BlockControllerRuntimeStatus, apiPath string) func(any, *uctypes.UIMessageDataToolUse) (any, error) {
return func(input any, toolUseData *uctypes.UIMessageDataToolUse) (any, error) {
if status.TsunamiPort == 0 {
return nil, fmt.Errorf("tsunami port not available")
}
url := fmt.Sprintf("http://localhost:%d%s", status.TsunamiPort, apiPath)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var reqBody []byteView on GitHub (pinned to a4447c1563)
Solutions
- Check the wrapped error (%w) to see the exact JSON syntax problem and dump the raw body with curl http://localhost:<TsunamiPort>/api/data to inspect what the widget actually returns.
- Verify the widget is a genuine Tsunami widget serving JSON and that TsunamiPort in the block runtime status points at the right process (not another service bound to the same port).
- Update or restart the Tsunami widget so its /api/data and /api/config endpoints emit valid JSON with 200.
- If the endpoint can legitimately return an empty body, fix the widget/server to return 'null' or '{}' so decoding succeeds.
Example fix
// before: client fails on empty/non-JSON 200 body from widget; // after: widget side always emits JSON, e.g. w.Header().Set("Content-Type", "application/json"); json.NewEncoder(w).Encode(data) (or write "null" for empty results) Defensive patterns
Strategy: try-catch
Validate before calling
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/api/data", port)); if err != nil || resp.StatusCode != 200 { /* skip tool call */ }; if !strings.Contains(resp.Header.Get("Content-Type"), "json") { /* likely non-JSON body; skip */ } Type guard
func isJSONContentType(ct string) bool { return strings.Contains(strings.ToLower(ct), "application/json") } Try / catch
result, err := toolCallback(nil, toolUseData); if err != nil { var jsonErr *json.SyntaxError; if errors.As(err, &jsonErr) { /* invalid JSON from widget: dump raw body, restart/upgrade widget */ }; return fmt.Errorf("tsunami getdata failed: %w", err) } Prevention
- Always inspect the wrapped error with errors.As for *json.SyntaxError/*json.UnmarshalTypeError to distinguish malformed vs truncated bodies.
- Curl the widget endpoint manually to confirm it returns JSON with Content-Type: application/json.
- Keep Tsunami widgets updated so /api/data and /api/config always emit valid JSON.
- Treat empty 200 bodies as failures and surface them early in tool diagnostics.
When it happens
Trigger: Calling the tsunami_getdata_<blockid> or tsunami_getconfig_<blockid> tool while the widget's HTTP endpoint responds 200 with a non-JSON body: empty response, HTML error page served with status 200, truncated body, or the widget writing plain text.
Common situations: A Tsunami widget version that returns plain text or an empty body on /api/data; a reverse proxy or dev server intercepting the port and returning an HTML page; the widget process crashing mid-response; Content-Type mismatches after widget framework upgrades.
Understand the failure class
Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- HTTP ${response.status}: ${response.statusText}
- terminal input request failed: ${response.status} ${response
- failed to marshal input: %w
- Invalid request body: %v
- failed to parse JSON: %v
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/5723ad951ff61a8e.
Report an issue: GitHub.