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 []byte

View on GitHub (pinned to a4447c1563)

Solutions

  1. 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.
  2. 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).
  3. Update or restart the Tsunami widget so its /api/data and /api/config endpoints emit valid JSON with 200.
  4. 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

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.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/5723ad951ff61a8e. Report an issue: GitHub.