wavetermdev/waveterm · error

failed to marshal input: %w

Error message

failed to marshal input: %w

What it means

This error wraps json.Marshal failure when serializing the tool's input argument before POSTing it to the Tsunami widget's /api/config endpoint. json.Marshal fails only for values JSON cannot represent (channels, funcs, cycles, or unsupported types). The underlying marshal error is preserved via %w.

Source

Thrown at pkg/aiusechat/tools_tsunami.go:98

}

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
		var err error
		if input != nil {
			reqBody, err = json.Marshal(input)
			if err != nil {
				return nil, fmt.Errorf("failed to marshal input: %w", err)
			}
		}

		req, err := http.NewRequestWithContext(ctx, "POST", url, strings.NewReader(string(reqBody)))
		if err != nil {
			return nil, fmt.Errorf("failed to create request: %w", err)
		}
		req.Header.Set("Content-Type", "application/json")

		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)
		}

View on GitHub (pinned to a4447c1563)

Solutions

  1. Read the wrapped error to identify the offending type, then ensure the input passed to the tool callback is JSON-compatible (plain maps, slices, strings, numbers, bools).
  2. Marshal the input yourself with json.Marshal (or utilfn.ReUnmarshal) before invoking the callback to catch the problem early with a clearer stack.
  3. If input comes from a non-JSON source, convert it to a JSON-safe representation first.
  4. For complex config values, register MarshalJSON on the custom type so it serializes correctly.

Example fix

// before: callback(map[string]any{"cb": func() {}}) fails with 'json: unsupported type: func()'; // after: callback(map[string]any{"refreshInterval": 30}) with JSON-safe input only
Defensive patterns

Strategy: validation

Validate before calling

if _, err := json.Marshal(input); err != nil { return fmt.Errorf("tool input is not JSON-serializable: %w", err) }

Type guard

func isJSONSafe(v any) bool { _, err := json.Marshal(v); return err == nil }

Try / catch

out, err := callback(input, toolUseData); if err != nil && strings.Contains(err.Error(), "failed to marshal input") { /* sanitize input to a JSON-safe map and retry once */ }

Prevention

When it happens

Trigger: Calling tsunami_setconfig with an input value containing non-JSON-serializable data (e.g. a map holding a func or channel, cyclic structures) - though inputs normally arrive as decoded JSON from the model, hand-crafted or programmatic callers can pass arbitrary any values.

Common situations: Programmatic/tool-harness callers passing Go-native values with unsupported types instead of plain JSON-compatible maps; custom tool runners injecting config objects containing cyclic references or unserializable field types.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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