{"record":{"id":"f63bd11dfc1b400e","repo":"chenhg5/cc-connect","slug":"empty-attachment-data","errorCode":null,"errorMessage":"empty attachment data","messagePattern":"empty attachment data","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":534,"sourceCode":"\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"max: edit message: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\trespBody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn fmt.Errorf(\"max: edit message: HTTP %d: %s\", resp.StatusCode, respBody)\n\t}\n\treturn nil\n}\n\n// uploadAttachment performs the two-step MAX upload: request an upload URL from\n// /uploads?type=<kind>, then POST the binary as multipart/form-data field \"data\"\n// to that URL. Returns the token to embed in a subsequent /messages attachment.\nfunc (p *Platform) uploadAttachment(ctx context.Context, kind string, data []byte, filename string) (string, error) {\n\tif len(data) == 0 {\n\t\treturn \"\", fmt.Errorf(\"empty attachment data\")\n\t}\n\t// Use a 5-minute context AND a dedicated http.Client with a matching Timeout.\n\t// p.client has a 35 s Timeout which fires independently of the context deadline\n\t// and would abort large CDN uploads before the context expires.\n\tuploadCtx, cancel := context.WithTimeout(ctx, attachmentUploadTO)\n\tdefer cancel()\n\n\turlReq, err := http.NewRequestWithContext(uploadCtx, http.MethodPost, p.apiBase+\"/uploads\", nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tp.setAuth(urlReq)\n\tq := urlReq.URL.Query()\n\tq.Set(\"type\", kind)\n\turlReq.URL.RawQuery = q.Encode()\n\n\turlResp, err := p.uploadClient.Do(urlReq)\n\tif err != nil {","sourceCodeStart":516,"sourceCodeEnd":552,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L516-L552","documentation":"uploadAttachment rejects calls where the data slice is empty before doing any network work. The two-step MAX upload (request upload URL, then multipart POST) is pointless with zero bytes, so the library fails fast with this sentinel error. It indicates the caller passed no attachment payload.","triggerScenarios":"Calling SendImage, SendFile, or SendAudio with an empty []byte payload — typically after a failed file read that returned (nil, nil), an unmarshaled attachment with no Data field, or a zero-length download.","commonSituations":"Reading a local file that exists but is 0 bytes; forgetting to check err before using file content from ioutil.ReadFile in an upstream wrapper; attachments forwarded from another platform with no body downloaded.","solutions":["Check the source of the attachment data — the upstream read probably silently produced an empty slice","Guard before calling: if len(data) == 0 { skip or return a descriptive error }","If the file is legitimately empty, decide policy: skip sending rather than call the API","Log the attachment filename/source to find why it is empty"],"exampleFix":"// before\ntoken, err := p.uploadAttachment(ctx, \"image\", data, \"pic.png\")\n// after\nif len(data) == 0 {\n    return fmt.Errorf(\"max: skipping %s: empty attachment data\", filename)\n}\ntoken, err := p.uploadAttachment(ctx, \"image\", data, \"pic.png\")","handlingStrategy":"validation","validationCode":"if len(data) == 0 { return fmt.Errorf(\"attachment %s is empty\", filename) }","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"empty attachment data\") {\n    slog.Warn(\"skipping empty attachment\", \"file\", filename)\n    return nil\n}","preventionTips":["Always check the error return of file reads before using the bytes","Verify file size > 0 with os.Stat before sending","Skip or placeholder-replace empty attachments instead of calling the upload path"],"tags":["validation","attachment","upload","empty-payload"],"backgroundTag":"empty-required-field","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}