OpenNHP/opennhp · error

could not close writer

Error message

could not close writer: %v

What it means

UploadFileToNHPServer builds a multipart/form-data body in memory for uploading a file to the NHP-DB server. After all parts are copied via io.Copy, it calls writer.Close() (multipart.Writer.Close), which writes the closing MIME boundary. If that fails, the multipart payload is truncated/incomplete and the function returns "could not close writer: %v" with the underlying cause.

Solutions

  1. Check available memory; the body is buffered entirely in RAM, so for large files switch to os.CreateTemp plus multipart.NewWriter(io.Writer) so Close() writes to disk instead of memory
  2. Read the wrapped error %v: if it is bytes.ErrTooLarge or an allocation failure, reduce file size or stream the upload
  3. Verify the file was fully copied before Close by checking the error (and progress.TotalSize) from the preceding io.Copy call
  4. Retry the upload after freeing memory; the failure is environmental, not a code bug in most cases

Example fix

// before
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
...
err = writer.Close()
if err != nil {
	return "", fmt.Errorf("could not close writer: %v", err)
}
// after
tmpFile, err := os.CreateTemp("", "nhp-upload-*")
if err != nil {
	return "", fmt.Errorf("could not create temp file: %v", err)
}
defer os.Remove(tmpFile.Name())
defer tmpFile.Close()
writer := multipart.NewWriter(tmpFile)
...
err = writer.Close()
if err != nil {
	return "", fmt.Errorf("could not close writer: %v", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

fi, err := os.Stat(filePath)
if err != nil { return err }
const maxInMemory = 512 << 20 // 512MB
if fi.Size() > maxInMemory {
	return fmt.Errorf("file %s too large (%d bytes) for in-memory multipart upload", filePath, fi.Size())
}

Type guard

func canBufferInMemory(size int64, limit int64) bool {
	return size > 0 && size <= limit
}

Try / catch

result, err := device.UploadFileToNHPServer(filePath)
if err != nil {
	var memErr *errors.errorString
	if strings.Contains(err.Error(), "could not close writer") || strings.Contains(err.Error(), "bytes.ErrTooLarge") {
		// free memory / use disk-backed upload path, then retry
	}
	return fmt.Errorf("upload failed: %w", err)
}

Prevention

When it happens

Trigger: multipart.Writer.Close() returns an error inside UploadFileToNHPServer (endpoints/db/utils.go:240-243) after io.Copy(part, progressReader) succeeded. Practically this happens when the underlying bytes.Buffer write fails (e.g. the buffer cannot grow due to memory exhaustion for very large files) or a part writer was used incorrectly before closing.

Common situations: Uploading an extremely large file that exhausts memory while the in-memory body buffer grows; running in a memory-capped container (OOM pressure) so the final boundary write fails; less commonly, custom part writers misused before Close.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/f3a497be339642b2. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/db/utils.go:242

	part, err := writer.CreateFormFile("file", filepath.Base(filePath))
	if err != nil {
		return "", fmt.Errorf("could not create form file: %v", err)
	}

	progressReader := &ProgressReader{
		Reader:   file,
		Progress: progress,
	}

	_, err = io.Copy(part, progressReader)
	if err != nil {
		return "", fmt.Errorf("could not copy file to server: %v", err)
	}

	err = writer.Close()
	if err != nil {
		return "", fmt.Errorf("could not close writer: %v", err)
	}

	uploadUrl := httpHost + "storage/upload"

	req, err := http.NewRequest("POST", uploadUrl, body)
	if err != nil {
		return "", fmt.Errorf("could not create request: %v", err)
	}

	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{
		Timeout: 120 * time.Minute,
	}

	resp, err := client.Do(req)
	if err != nil {
		return "", fmt.Errorf("could not send https request: %v", err)

View on GitHub (pinned to 6e04ca5ff0)