vxcontrol/pentagi · error
failed to compute MD5: %w
Error message
failed to compute MD5: %w
What it means
ComputeFileMD5 (resources.go:75) reads an io.Reader to EOF and returns the lowercase hex MD5 digest. The error 'failed to compute MD5: %w' is returned when io.Copy fails while streaming bytes into the md5 hash. It wraps the underlying read error, so the root cause (disk failure, broken pipe, permission error on a file-backed reader) is available via errors.Unwrap or errors.As/Is.
Source
Thrown at backend/pkg/resources/resources.go:75
if !IsValidBlobHash(cleanHash) {
return filepath.Join(ResourcesDir(dataDir), invalidBlobHashFileName)
}
return filepath.Join(ResourcesDir(dataDir), cleanHash+".blob")
}
// EnsureResourcesDir creates the resources storage directory if it does not exist.
func EnsureResourcesDir(dataDir string) error {
if err := os.MkdirAll(ResourcesDir(dataDir), 0755); err != nil {
return fmt.Errorf("failed to create resources directory: %w", err)
}
return nil
}
// ComputeFileMD5 reads r to EOF and returns the lowercase hex MD5 digest.
func ComputeFileMD5(r io.Reader) (string, error) {
h := md5.New()
if _, err := io.Copy(h, r); err != nil {
return "", fmt.Errorf("failed to compute MD5: %w", err)
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// IsValidBlobHash reports whether hash is a hex-encoded MD5 digest.
func IsValidBlobHash(hash string) bool {
if len(hash) != md5.Size*2 {
return false
}
for _, r := range hash {
if (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') {
continue
}
return false
}
return true
}
View on GitHub (pinned to ea665308ba)
Solutions
- Inspect the wrapped cause with errors.Unwrap(err) or %v of the error to find the real I/O failure
- Re-check the source file exists and is readable (os.Stat, permissions) before hashing
- For streams, retry the read/hash from the start of the stream; partial hashing is not recoverable
- If the reader can legitimately fail mid-copy, wrap it with buffering or read it fully into memory only if size permits
Example fix
// before
hash, err := resources.ComputeFileMD5(f) // f may be stale/closed
if err != nil { return err }
// after
if _, err := f.Stat(); err != nil { return fmt.Errorf("source unavailable: %w", err) }
if _, err := f.Seek(0, io.SeekStart); err != nil { return err }
hash, err := resources.ComputeFileMD5(f)
if err != nil { return fmt.Errorf("hashing failed: %w", err) } Defensive patterns
Strategy: try-catch
Validate before calling
if f, ok := r.(*os.File); ok {
if _, err := f.Stat(); err != nil {
return fmt.Errorf("source unreadable: %w", err)
}
} Type guard
var _ = func(r io.Reader) bool { return r != nil } Try / catch
hash, err := resources.ComputeFileMD5(r)
if err != nil {
var perr *fs.PathError
if errors.As(err, &perr) { /* handle open/read failure on path */ }
return fmt.Errorf("md5 compute failed: %w", err)
} Prevention
- Stat the source file (exists, readable, size stable) before hashing
- For network streams, buffer or retry the whole stream rather than resuming a partial hash
- Never hash a file that is still being written; wait for the writer to finish
- Log errors.Unwrap(err) to distinguish source I/O issues from hashing bugs
When it happens
Trigger: Calling ComputeFileMD5 with a reader whose underlying source fails during read: an *os.File opened on a removed or unreadable file, a network/HTTP body that drops mid-stream, or a bytes.Reader is fine but a custom reader returning an error.
Common situations: Hashing a file that was truncated or deleted between open and read; hashing an upload stream that was interrupted; permission changes on the source file; storage device errors in Docker volume backends.
Related errors
- image download stream processing failed: %w
- failed to create temporary upload file: %w
- failed to write temporary upload file: %w
- failed to read tar entry: %w
- failed to create temp directory: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/903d0d8edbfd2229.
Report an issue: GitHub.