vxcontrol/pentagi · error

failed to stat blob %s: %w

Error message

failed to stat blob %s: %w

What it means

ZipResources stats every blob with os.Lstat before writing any bytes to the ZIP writer, because the HTTP caller may have already sent a 200 status by the time the first byte is written — a missing blob mid-stream would yield a truncated archive under a success code. This error reports which blob path could not be stat'ed and why.

Source

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

		// Already exists — remove tmp and consider success.
		os.Remove(tmpPath)
		return nil
	}
	if err := os.Rename(tmpPath, dest); err != nil {
		return fmt.Errorf("failed to commit blob %s: %w", hash, err)
	}
	return nil
}

// ZipResources writes a ZIP archive to w containing all entries in files.
// Each ZipEntry maps a .blob file on disk to a path inside the archive.
func ZipResources(w io.Writer, entries []ZipEntry) (err error) {
	// The streaming HTTP caller commits its 200 status on the first byte written,
	// so a missing blob must be caught before then, or the client gets a truncated
	// archive under 200. Stat all blobs up front; don't fold into the write loop.
	for _, e := range entries {
		if _, statErr := os.Lstat(e.BlobPath); statErr != nil {
			return fmt.Errorf("failed to stat blob %s: %w", e.BlobPath, statErr)
		}
	}

	zw := zip.NewWriter(w)
	defer func() {
		if cerr := zw.Close(); err == nil {
			err = cerr
		}
	}()

	for _, e := range entries {
		info, err := os.Lstat(e.BlobPath)
		if err != nil {
			return fmt.Errorf("failed to stat blob %s: %w", e.BlobPath, err)
		}
		if !info.Mode().IsRegular() {
			continue
		}

View on GitHub (pinned to ea665308ba)

Solutions

  1. Verify the blob file exists at the exact BlobPath (ls the path from the error) and fix the path construction
  2. Check hex-case consistency: md5 hashes must be produced and looked up with the same encoding (hex.EncodeToString is lowercase)
  3. Check for GC/cleanup jobs removing .blob files before download and coordinate retention windows
  4. Verify the blob storage root configuration matches between upload and download paths

Example fix

// before
blobPath := filepath.Join(root, strings.ToUpper(hash)) // wrong case
// after
blobPath := filepath.Join(root, hex.EncodeToString(sum)) // lowercase, matches CommitBlob
Defensive patterns

Strategy: validation

Validate before calling

func validateZipEntries(entries []resources.ZipEntry) error {
	for _, e := range entries {
		if _, err := os.Lstat(e.BlobPath); err != nil {
			return fmt.Errorf("blob missing: %s: %w", e.BlobPath, err)
		}
	}
	return nil
}

Try / catch

err := resources.ZipResources(w, entries)
if err != nil {
	if strings.Contains(err.Error(), "failed to stat blob") {
		log.Error("blob missing; refusing to send truncated archive", "err", err)
		// respond 500 BEFORE any bytes are written to w
		return err
	}
	return err
}

Prevention

When it happens

Trigger: Calling ZipResources with a ZipEntry whose BlobPath doesn't exist — the .blob file was never committed, was cleaned up, or the path was constructed with the wrong hash/directory. Raised via resource-download flows.

Common situations: Content-addressed store pruned by a GC job between listing resources and downloading the archive; hash computed differently at commit vs. lookup (e.g. uppercase hex vs lowercase); blob directory misconfigured so entries point at the wrong root.

Related errors


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