anomalyco/sst · error
failed to close writer: %w
Error message
failed to close writer: %w
What it means
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.
Source
Thrown at pkg/server/resource/cloudflare-worker-assets.go:259
encodedContent := base64.StdEncoding.EncodeToString(fileContent)
// Create form field with content type header
part, err := writer.CreatePart(map[string][]string{
"Content-Disposition": []string{fmt.Sprintf(`form-data; name="%s"; filename="%s"`, hash, hash)},
"Content-Type": []string{contentType},
})
if err != nil {
return "", fmt.Errorf("failed to create form part %s: %w", hash, err)
}
_, err = part.Write([]byte(encodedContent))
if err != nil {
return "", fmt.Errorf("failed to write encoded content for %s: %w", hash, err)
}
}
err := writer.Close()
if err != nil {
return "", fmt.Errorf("failed to close writer: %w", err)
}
url := fmt.Sprintf("https://api.cloudflare.com/client/v4/accounts/%s/workers/assets/upload?base64=true", accountId)
req, err := http.NewRequest("POST", url, &body)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "multipart/form-data; boundary="+writer.Boundary())
req.Header.Set("Authorization", "Bearer "+jwt)
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
View on GitHub (pinned to a0bd20f762)
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
Example fix
// before
err := writer.Close()
if err != nil {
return "", fmt.Errorf("failed to close writer: %w", err)
}
// after
if err := writer.Close(); err != nil {
return "", fmt.Errorf("failed to close writer: %w", err)
}
// verify body non-empty before sending
if body.Len() == 0 {
return "", fmt.Errorf("empty multipart body for asset upload")
} Defensive patterns
Strategy: try-catch
Validate before calling
if body.Len() == 0 {
return fmt.Errorf("multipart body is empty; no parts were written")
} Try / catch
if err := writer.Close(); err != nil {
return "", fmt.Errorf("failed to close writer: %w", err)
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Closing the writer twice; the underlying bytes.Buffer previously errored; sending a truncated body that Cloudflare then rejects (masked as an HTTP error).
Related errors
- failed to create form part %s: %w
- failed to complete asset upload - no completion JWT received
- failed to write encoded content for %s: %w
- failed to upload assets: HTTP %d %s
- bucket %v upload failed: %w
AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30).
Data as JSON: /api/errors/2f2af6eb24e7c929.
Report an issue: GitHub.