{"record":{"id":"cf937759f02d4cfa","repo":"wavetermdev/waveterm","slug":"parts-must-not-be-empty","errorCode":null,"errorMessage":"parts must not be empty","messagePattern":"parts must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/uctypes/uctypes.go","lineNumber":385,"sourceCode":"\ntype AIToolResult struct {\n\tToolName  string `json:\"toolname\"`\n\tToolUseID string `json:\"tooluseid\"`\n\tErrorText string `json:\"errortext,omitempty\"`\n\tText      string `json:\"text,omitempty\"`\n}\n\nfunc (m *AIMessage) GetMessageId() string {\n\treturn m.MessageId\n}\n\nfunc (m *AIMessage) Validate() error {\n\tif m.MessageId == \"\" {\n\t\treturn fmt.Errorf(\"messageid must be set\")\n\t}\n\n\tif len(m.Parts) == 0 {\n\t\treturn fmt.Errorf(\"parts must not be empty\")\n\t}\n\n\tfor i, part := range m.Parts {\n\t\tif err := part.Validate(); err != nil {\n\t\t\treturn fmt.Errorf(\"part %d: %w\", i, err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (p *AIMessagePart) Validate() error {\n\tif p.Type == AIMessagePartTypeText {\n\t\tif p.Text == \"\" {\n\t\t\treturn fmt.Errorf(\"text type requires non-empty text field\")\n\t\t}\n\t\t// Check that no file fields are set\n\t\tif p.FileName != \"\" || p.MimeType != \"\" || len(p.Data) > 0 || p.URL != \"\" {","sourceCodeStart":367,"sourceCodeEnd":403,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/uctypes/uctypes.go#L367-L403","documentation":"AIMessage.Validate requires at least one part; a message with an empty Parts slice is structurally meaningless in the UI message protocol and is rejected. This guarantees downstream consumers never iterate zero parts when rendering or processing the conversation.","triggerScenarios":"Validate() on an AIMessage with MessageId set but len(Parts) == 0 — e.g. deserializing {\"messageid\":\"x\",\"parts\":[]}, dropping all parts during transformation/filtering, or building a message before appending content.","commonSituations":"Streaming code that creates the message shell before parts arrive and validates too early; filtering logic that removes all invalid parts; serializers that omit empty slices and round-trip to empty.","solutions":["Append at least one valid part (text/tool-use/etc.) before validating","If the message is a streaming placeholder, defer Validate() until parts are populated","Check upstream filtering that may be stripping all parts","Fix the serializer so parts are not lost (e.g. omit-empty on the wrong field)"],"exampleFix":"// before\nmsg := &uctypes.AIMessage{MessageId: id}  // no parts\nerr := msg.Validate() // \"parts must not be empty\"\n// after\nmsg := &uctypes.AIMessage{MessageId: id, Parts: []uctypes.MessagePart{uctypes.NewTextPart(\"hello\")}}\nerr := msg.Validate()","handlingStrategy":"validation","validationCode":"if len(msg.Parts) == 0 {\n    return fmt.Errorf(\"AIMessage %s requires at least one part\", msg.MessageId)\n}","typeGuard":"func hasParts(m *uctypes.AIMessage) bool {\n    return m != nil && len(m.Parts) > 0\n}","tryCatchPattern":"if err := msg.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"parts must not be empty\") {\n        // defer validation until parts are streamed in, or append a placeholder part\n    }\n    return err\n}","preventionTips":["Do not validate streaming placeholder messages before parts arrive","Ensure filtering logic never removes all parts","Verify serializers round-trip the parts array","Append content before constructing the AIMessage"],"tags":["validation","message-protocol","empty-payload"],"backgroundTag":"schema-validation-failed","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}