{"record":{"id":"bdabcb2dcb2f0b96","repo":"wavetermdev/waveterm","slug":"failed-to-unmarshal-input-w","errorCode":null,"errorMessage":"failed to unmarshal input: %w","messagePattern":"failed to unmarshal input: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/tools_term.go","lineNumber":68,"sourceCode":"\t)\n\n\tresult := &TermGetScrollbackToolInput{\n\t\tLineStart: 0,\n\t\tCount:     0,\n\t}\n\n\tif input == nil {\n\t\tresult.Count = DefaultCount\n\t\treturn result, nil\n\t}\n\n\tinputBytes, err := json.Marshal(input)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal input: %w\", err)\n\t}\n\n\tif err := json.Unmarshal(inputBytes, result); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal input: %w\", err)\n\t}\n\n\tif result.Count == 0 {\n\t\tresult.Count = DefaultCount\n\t}\n\n\tif result.Count < 0 {\n\t\treturn nil, fmt.Errorf(\"count must be positive\")\n\t}\n\n\tresult.Count = min(result.Count, MaxCount)\n\n\treturn result, nil\n}\n\nfunc getTermScrollbackOutput(tabId string, widgetId string, rpcData wshrpc.CommandTermGetScrollbackLinesData) (*TermGetScrollbackToolOutput, error) {\n\tctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second)\n\tdefer cancelFn()","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/tools_term.go#L50-L86","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","solutions":["Read the wrapped *json.UnmarshalTypeError to see which field/type mismatched","Coerce common LLM type slips before parsing: accept numeric strings for count/line_start and stringify numeric widget_id","Re-prompt or return a schema reminder so the model emits widget_id as string and count/line_start as integers","Validate the input map's types manually before the round-trip if inputs are untrusted"],"exampleFix":"// before\nif err := json.Unmarshal(inputBytes, result); err != nil {\n    return nil, fmt.Errorf(\"failed to unmarshal input: %w\", err)\n}\n// after\nif err := json.Unmarshal(inputBytes, result); err != nil {\n    var typeErr *json.UnmarshalTypeError\n    if errors.As(err, &typeErr) {\n        return nil, fmt.Errorf(\"field %q has wrong type (expected %s): %w\", typeErr.Field, typeErr.Type, err)\n    }\n    return nil, fmt.Errorf(\"failed to unmarshal input: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"func validateScrollbackInput(m map[string]any) error {\n    if v, ok := m[\"widget_id\"]; ok { if _, ok := v.(string); !ok { return fmt.Errorf(\"widget_id must be a string\") } }\n    for _, k := range []string{\"count\", \"line_start\"} {\n        if v, ok := m[k]; ok {\n            switch n := v.(type) {\n            case float64:\n                if n != float64(int(n)) { return fmt.Errorf(\"%s must be an integer\", k) }\n            case string:\n                if _, err := strconv.Atoi(n); err != nil { return fmt.Errorf(\"%s must be an integer\", k) }\n            default:\n                return fmt.Errorf(\"%s must be an integer\", k)\n            }\n        }\n    }\n    return nil\n}","typeGuard":"func asInt(v any) (int, bool) {\n    switch n := v.(type) {\n    case float64: return int(n), true\n    case json.Number: i, err := n.Int64(); return int(i), err == nil\n    case string: i, err := strconv.Atoi(n); return i, err == nil\n    }\n    return 0, false\n}","tryCatchPattern":"parsed, err := parseTermGetScrollbackInput(input)\nif err != nil {\n    var typeErr *json.UnmarshalTypeError\n    if errors.As(err, &typeErr) {\n        return nil, fmt.Errorf(\"bad tool argument %q: expected %s\", typeErr.Field, typeErr.Type)\n    }\n    return nil, err\n}","preventionTips":["Mark the tool Strict: true (like capture_screenshot) so the model's arguments are schema-validated","Coerce numeric strings to ints before the round-trip for LLM robustness","Include explicit type descriptions in InputSchema and re-prompt on type errors"],"tags":["json","unmarshal","type-error","tool-input","wave"],"backgroundTag":"json-unmarshal-type-mismatch","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}