wavetermdev/waveterm · error

failed to unmarshal input: %w

Error message

failed to unmarshal input: %w

What it means

This wraps the json.Unmarshal half of the marshal/unmarshal round-trip in parseTermGetScrollbackInput, which re-decodes the marshaled input into *TermGetScrollbackToolInput. It fires when the input's shape does not match the struct's expected JSON types — most commonly when the AI model passes wrong types (e.g. count as a string "200" or widget_id as a number) so unmarshal fails with a json.UnmarshalTypeError.

Source

Thrown at pkg/aiusechat/tools_term.go:68

	)

	result := &TermGetScrollbackToolInput{
		LineStart: 0,
		Count:     0,
	}

	if input == nil {
		result.Count = DefaultCount
		return result, nil
	}

	inputBytes, err := json.Marshal(input)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal input: %w", err)
	}

	if err := json.Unmarshal(inputBytes, result); err != nil {
		return nil, fmt.Errorf("failed to unmarshal input: %w", err)
	}

	if result.Count == 0 {
		result.Count = DefaultCount
	}

	if result.Count < 0 {
		return nil, fmt.Errorf("count must be positive")
	}

	result.Count = min(result.Count, MaxCount)

	return result, nil
}

func getTermScrollbackOutput(tabId string, widgetId string, rpcData wshrpc.CommandTermGetScrollbackLinesData) (*TermGetScrollbackToolOutput, error) {
	ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancelFn()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Read the wrapped *json.UnmarshalTypeError to see which field/type mismatched
  2. Coerce common LLM type slips before parsing: accept numeric strings for count/line_start and stringify numeric widget_id
  3. Re-prompt or return a schema reminder so the model emits widget_id as string and count/line_start as integers
  4. Validate the input map's types manually before the round-trip if inputs are untrusted

Example fix

// before
if err := json.Unmarshal(inputBytes, result); err != nil {
    return nil, fmt.Errorf("failed to unmarshal input: %w", err)
}
// after
if err := json.Unmarshal(inputBytes, result); err != nil {
    var typeErr *json.UnmarshalTypeError
    if errors.As(err, &typeErr) {
        return nil, fmt.Errorf("field %q has wrong type (expected %s): %w", typeErr.Field, typeErr.Type, err)
    }
    return nil, fmt.Errorf("failed to unmarshal input: %w", err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

func validateScrollbackInput(m map[string]any) error {
    if v, ok := m["widget_id"]; ok { if _, ok := v.(string); !ok { return fmt.Errorf("widget_id must be a string") } }
    for _, k := range []string{"count", "line_start"} {
        if v, ok := m[k]; ok {
            switch n := v.(type) {
            case float64:
                if n != float64(int(n)) { return fmt.Errorf("%s must be an integer", k) }
            case string:
                if _, err := strconv.Atoi(n); err != nil { return fmt.Errorf("%s must be an integer", k) }
            default:
                return fmt.Errorf("%s must be an integer", k)
            }
        }
    }
    return nil
}

Type guard

func asInt(v any) (int, bool) {
    switch n := v.(type) {
    case float64: return int(n), true
    case json.Number: i, err := n.Int64(); return int(i), err == nil
    case string: i, err := strconv.Atoi(n); return i, err == nil
    }
    return 0, false
}

Try / catch

parsed, err := parseTermGetScrollbackInput(input)
if err != nil {
    var typeErr *json.UnmarshalTypeError
    if errors.As(err, &typeErr) {
        return nil, fmt.Errorf("bad tool argument %q: expected %s", typeErr.Field, typeErr.Type)
    }
    return nil, err
}

Prevention

When it happens

Trigger: term_get_scrollback invoked with input whose field types conflict with TermGetScrollbackToolInput: count or line_start as a non-integer (string, float with fraction, bool), or widget_id as a non-string (number, object, array).

Common situations: LLM emits tool arguments with wrong JSON types (count: "200", widget_id: 12345); upstream harness passes a JSON array or scalar instead of an object; schema not enforced (this tool is not Strict, so malformed arguments can reach the parser).

Related errors


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