{"record":{"id":"a7591ced266d90d6","repo":"wavetermdev/waveterm","slug":"text-plain-file-part-missing-data","errorCode":null,"errorMessage":"text/plain file part missing data","messagePattern":"text/plain file part missing data","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/aiusechat/aiutil/aiutil.go","lineNumber":100,"sourceCode":"\treturn \"\", fmt.Errorf(\"file part missing both url and data\")\n}\n\n// ExtractTextData extracts text data from either Data field or URL field (data: URLs only)\nfunc ExtractTextData(data []byte, url string) ([]byte, error) {\n\tif len(data) > 0 {\n\t\treturn data, nil\n\t}\n\tif url != \"\" {\n\t\tif strings.HasPrefix(url, \"data:\") {\n\t\t\t_, decodedData, err := utilfn.DecodeDataURL(url)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to decode data URL for text/plain file: %w\", err)\n\t\t\t}\n\t\t\treturn decodedData, nil\n\t\t}\n\t\treturn nil, fmt.Errorf(\"dropping text/plain file with URL (must be fetched and converted to data)\")\n\t}\n\treturn nil, fmt.Errorf(\"text/plain file part missing data\")\n}\n\n// FormatAttachedTextFile formats a text file attachment with proper encoding and deterministic suffix\nfunc FormatAttachedTextFile(fileName string, textContent []byte) string {\n\tif fileName == \"\" {\n\t\tfileName = \"untitled.txt\"\n\t}\n\n\tencodedFileName := strings.ReplaceAll(fileName, `\"`, \"&quot;\")\n\tquotedFileName := strconv.Quote(encodedFileName)\n\n\ttextStr := string(textContent)\n\tdeterministicSuffix := GenerateDeterministicSuffix(textStr, fileName)\n\treturn fmt.Sprintf(\"<AttachedTextFile_%s file_name=%s>\\n%s\\n</AttachedTextFile_%s>\", deterministicSuffix, quotedFileName, textStr, deterministicSuffix)\n}\n\n// FormatAttachedDirectoryListing formats a directory listing attachment with proper encoding and deterministic suffix\nfunc FormatAttachedDirectoryListing(directoryName, jsonContent string) string {","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/aiusechat/aiutil/aiutil.go#L82-L118","documentation":"Indicates a text/plain file part in an AI message is missing its data payload, so the file cannot be attached or converted.","triggerScenarios":"Calling ExtractTextData (directly or via convertFileAIMessagePart/convertAIMessageTextOnly/convertAIMessageMultimodal) with data == nil/empty AND url == \"\" — e.g. a file part where only FileName/MimeType were set.","commonSituations":"Constructing UIMessageParts for text attachments without reading the file; content lost during serialization/deserialization; a client that sent the attachment metadata but failed to include the body.","solutions":["Read the text file and pass its bytes as the data argument.","Populate the URL field with a data: URL containing the text content.","Skip the empty part and warn instead of calling ExtractTextData with no content.","Add a pre-check: if len(data) == 0 && url == \"\" return/skip the part early."],"exampleFix":"// before\npart := uctypes.UIMessageDataUserFile{FileName: \"a.txt\", MimeType: \"text/plain\"}\n_, err := aiutil.ExtractTextData(nil, \"\") // missing data\n// after\ncontent, _ := os.ReadFile(\"a.txt\")\n_, err := aiutil.ExtractTextData(content, \"\")","handlingStrategy":"validation","validationCode":"func hasTextContent(data []byte, url string) bool { return len(data) > 0 || url != \"\" }\nif !hasTextContent(part.Data, part.URL) { return fmt.Errorf(\"text file %q has no content\", part.FileName) }","typeGuard":null,"tryCatchPattern":"content, err := aiutil.ExtractTextData(data, url)\nif err != nil {\n    return nil, fmt.Errorf(\"attachment %s unusable, skipping: %w\", fileName, err)\n}","preventionTips":["Verify Data or URL is populated before building text file parts","Check serialization round-trips preserve the Data field","Reject empty attachments at the ingestion boundary with a user-facing warning"],"tags":["text-attachment","missing-content","input-validation"],"backgroundTag":"missing-file-content","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}