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

  1. Inspect errors.Unwrap(err) for the underlying writer failure
  2. Ensure Close is called exactly once, after all parts are written
  3. Rebuild the multipart body and retry the upload
  4. 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

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


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/2f2af6eb24e7c929. Report an issue: GitHub.