{"record":{"id":"2f2af6eb24e7c929","repo":"anomalyco/sst","slug":"failed-to-close-writer-w","errorCode":null,"errorMessage":"failed to close writer: %w","messagePattern":"failed to close writer: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/server/resource/cloudflare-worker-assets.go","lineNumber":259,"sourceCode":"\t\tencodedContent := base64.StdEncoding.EncodeToString(fileContent)\n\t\t\n\t\t// Create form field with content type header\n\t\tpart, err := writer.CreatePart(map[string][]string{\n\t\t\t\"Content-Disposition\": []string{fmt.Sprintf(`form-data; name=\"%s\"; filename=\"%s\"`, hash, hash)},\n\t\t\t\"Content-Type\":        []string{contentType},\n\t\t})\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to create form part %s: %w\", hash, err)\n\t\t}\n\n\t\t_, err = part.Write([]byte(encodedContent))\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"failed to write encoded content for %s: %w\", hash, err)\n\t\t}\n\t}\n\terr := writer.Close()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to close writer: %w\", err)\n\t}\n\n\turl := fmt.Sprintf(\"https://api.cloudflare.com/client/v4/accounts/%s/workers/assets/upload?base64=true\", accountId)\n\n\treq, err := http.NewRequest(\"POST\", url, &body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"multipart/form-data; boundary=\"+writer.Boundary())\n\treq.Header.Set(\"Authorization\", \"Bearer \"+jwt)\n\n\tclient := &http.Client{Timeout: 30 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer resp.Body.Close()\n","sourceCodeStart":241,"sourceCodeEnd":277,"githubUrl":"https://github.com/anomalyco/sst/blob/a0bd20f762883e72a35caccb4896c42ce5b3f707/pkg/server/resource/cloudflare-worker-assets.go#L241-L277","documentation":"Once all form parts are written, writer.Close() finalizes the multipart body by writing the closing boundary. This error wraps a Close failure. With an in-memory bytes.Buffer backing the writer this almost never happens, but it is checked because a failed Close means the request body is truncated and the Cloudflare upload would be rejected.","triggerScenarios":"writer.Close() returns non-nil err in uploadAssets (pkg/server/resource/cloudflare-worker-assets.go:259) after the asset loop; caused by an I/O failure on the underlying writer or closing an already-failed/closed multipart writer.","commonSituations":"Closing the writer twice; the underlying bytes.Buffer previously errored; sending a truncated body that Cloudflare then rejects (masked as an HTTP error).","solutions":["Inspect errors.Unwrap(err) for the underlying writer failure","Ensure Close is called exactly once, after all parts are written","Rebuild the multipart body and retry the upload","If the buffer write path can fail (huge payloads), check earlier writes for silent errors"],"exampleFix":"// before\nerr := writer.Close()\nif err != nil {\n\treturn \"\", fmt.Errorf(\"failed to close writer: %w\", err)\n}\n// after\nif err := writer.Close(); err != nil {\n\treturn \"\", fmt.Errorf(\"failed to close writer: %w\", err)\n}\n// verify body non-empty before sending\nif body.Len() == 0 {\n\treturn \"\", fmt.Errorf(\"empty multipart body for asset upload\")\n}","handlingStrategy":"try-catch","validationCode":"if body.Len() == 0 {\n\treturn fmt.Errorf(\"multipart body is empty; no parts were written\")\n}","typeGuard":null,"tryCatchPattern":"if err := writer.Close(); err != nil {\n\treturn \"\", fmt.Errorf(\"failed to close writer: %w\", err)\n}","preventionTips":["Call Close exactly once, after all parts are written","Verify the resulting body is non-empty before issuing the HTTP request","Do not discard errors from earlier part writes; they corrupt Close state"],"tags":["cloudflare","multipart","workers-assets","upload"],"backgroundTag":"multipart-close-failed","analyzedSha":"a0bd20f762883e72a35caccb4896c42ce5b3f707","analyzedAt":"2026-08-30T11:26:00.383Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}