vxcontrol/pentagi · error

failed to write temp file: %w

Error message

failed to write temp file: %w

What it means

SaveToTemp streams the reader into the temp file and an MD5 hasher via io.Copy; this error wraps a copy failure. It indicates the upload data could not be fully read or written — a truncated upload, client disconnect, or disk error. The temp file is removed before returning.

Source

Thrown at backend/pkg/resources/resources.go:290

// the temp file. The caller is responsible for removing the temp file on error.
func SaveToTemp(r io.Reader, dir string) (tmpPath string, hash string, size int64, err error) {
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", "", 0, fmt.Errorf("failed to create temp directory: %w", err)
	}

	tmp, err := os.CreateTemp(dir, ".resource-upload-*")
	if err != nil {
		return "", "", 0, fmt.Errorf("failed to create temp file: %w", err)
	}
	tmpPath = tmp.Name()
	defer tmp.Close()

	h := md5.New()
	mw := io.MultiWriter(tmp, h)
	written, copyErr := io.Copy(mw, r)
	if copyErr != nil {
		os.Remove(tmpPath)
		return "", "", 0, fmt.Errorf("failed to write temp file: %w", copyErr)
	}
	if err := tmp.Chmod(0644); err != nil {
		os.Remove(tmpPath)
		return "", "", 0, fmt.Errorf("failed to set temp file permissions: %w", err)
	}

	return tmpPath, hex.EncodeToString(h.Sum(nil)), written, nil
}

// CommitBlob atomically moves tmpPath to the .blob destination for hash.  If
// the blob already exists (race with concurrent upload of identical file) the
// tmp file is removed and no error is returned.
func CommitBlob(dataDir, hash, tmpPath string) error {
	if err := validateBlobHash(hash); err != nil {
		return err
	}
	if err := EnsureResourcesDir(dataDir); err != nil {
		return err

View on GitHub (pinned to ea665308ba)

Solutions

  1. Check disk space (df -h) and quotas on the temp directory volume
  2. Increase proxy/server upload timeouts (e.g. nginx client_body_timeout, proxy_read_timeout) for large files
  3. Retry the upload from the client; the error is transient for network causes
  4. Wrap the reader with logging to distinguish client-disconnect from disk errors before calling SaveToTemp

Example fix

// before
# nginx default 60s read timeout kills big uploads
proxy_read_timeout 60s;
// after
proxy_read_timeout 600s;
client_max_body_size 2G;
Defensive patterns

Strategy: try-catch

Validate before calling

func diskHasSpace(dir string, min uint64) error {
	var st syscall.Statfs_t
	if err := syscall.Statfs(dir, &st); err != nil {
		return err
	}
	avail := st.Bavail * uint64(st.Bsize)
	if avail < min {
		return fmt.Errorf("only %d bytes free in %s", avail, dir)
	}
	return nil
}

Try / catch

tmpPath, hash, size, err := resources.SaveToTemp(body, dir)
if err != nil {
	if strings.Contains(err.Error(), "failed to write temp file") {
		if errors.Is(context.Cause(bodyCtx), context.Canceled) {
			return nil // client disconnected; nothing to do
		}
		log.Error("upload copy failed", "err", err) // check disk/proxy timeouts
		return http.StatusInsufficientStorage
	}
	return err
}

Prevention

When it happens

Trigger: HTTP request body closed mid-upload (client disconnect/timeout); disk full while writing; underlying reader (network stream, multipart part) returning an I/O error. Raised via UploadResources.

Common situations: Large file uploads exceeding a reverse-proxy body timeout (nginx proxy_read_timeout); user cancels upload in browser; diskquota exceeded on the container volume; flaky network between proxies.

Related errors


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/9f93892710dfc57b. Report an issue: GitHub.