{"record":{"id":"67742041647cd700","repo":"chenhg5/cc-connect","slug":"create-form-file-w-677420","errorCode":null,"errorMessage":"create form file: %w","messagePattern":"create form file: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1352,"sourceCode":"\treturn stdout.Bytes(), \"mp3\", nil\n}\n\n// uploadMedia uploads a file to DingTalk media API and returns the media ID.\n// mediaType should be \"voice\" or \"image\".\nfunc (p *Platform) uploadMedia(ctx context.Context, data []byte, fileName, mediaType string) (string, error) {\n\ttoken, err := p.getAccessToken()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"get access token: %w\", err)\n\t}\n\n\tuploadURL := fmt.Sprintf(\"https://oapi.dingtalk.com/media/upload?access_token=%s&type=%s\", token, mediaType)\n\n\tbody := bytes.NewBuffer(nil)\n\twriter := multipart.NewWriter(body)\n\n\tpart, err := writer.CreateFormFile(\"media\", fileName)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create form file: %w\", err)\n\t}\n\n\tif _, err := part.Write(data); err != nil {\n\t\treturn \"\", fmt.Errorf(\"write media data: %w\", err)\n\t}\n\n\tif err := writer.Close(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"close multipart writer: %w\", err)\n\t}\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadURL, body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"create upload request: %w\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", writer.FormDataContentType())\n\n\tresp, err := p.httpClient.Do(req)","sourceCodeStart":1334,"sourceCodeEnd":1370,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1334-L1370","documentation":"multipart.Writer.CreateFormFile failed while building the multipart body for the DingTalk media upload. This is rare — it only errors when the writer has already been closed or its internal boundary is malformed — and it indicates a programming/state bug rather than a network or server issue.","triggerScenarios":"Calling writer.CreateFormFile(\"media\", fileName) after the writer was closed or the underlying buffer state is invalid during uploadMedia.","commonSituations":"Refactored upload code that closes the writer before writing all parts; reuse of a single multipart.Writer across concurrent uploads; fileName containing pathological characters is NOT a cause here (CreateFormFile escapes only the filename into the header, it does not validate).","solutions":["Check call ordering in uploadMedia: create form file → write data → then Close, exactly once.","Do not share the multipart.Writer between goroutines; build one body per upload.","If you added custom parts before/after, ensure no part creation happens after Close."],"exampleFix":"// before\nwriter.Close()\npart, err := writer.CreateFormFile(\"media\", fileName) // too late\n// after\npart, err := writer.CreateFormFile(\"media\", fileName)\n// ... write data ...\nwriter.Close()","handlingStrategy":"type-guard","validationCode":"// Ensure writer state before creating the form file\nselect {\ncase <-writerClosed:\n    return errors.New(\"multipart writer already closed\")\ndefault:\n}","typeGuard":"func writerOpen(w *multipart.Writer, closed *atomic.Bool) bool { return !closed.Load() }","tryCatchPattern":null,"preventionTips":["Track writer lifecycle with a closed flag or single function scope.","Never call CreateFormFile after Close; keep build order: parts → data → Close.","One multipart.Writer per request; no sharing across goroutines."],"tags":["dingtalk","multipart","upload"],"backgroundTag":"multipart-form-error","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"}