vxcontrol/pentagi · error
blob for resource not found on disk
Error message
blob for resource %q not found on disk
What it means
The resource's metadata row exists, but the content blob file backing it (content-addressed by hash under the server dataDir) is missing on disk. The service returns 404 ErrResourcesNotFound rather than 500 because the object is unrecoverable through the API.
Solutions
- Restore the blob store / dataDir volume from backup
- Re-upload the resource to recreate its blob (then delete the dangling row if needed)
- Verify the DATA_DIR env and container volume mounts point to the persistent store
- Run a consistency check reconciling user_resources hashes against files on disk
Example fix
// before
docker compose up -d # dataDir volume not declared → blobs lost on redeploy
// after
volumes:
- pentagi-data:/data # persist blob storage
services:
backend:
volumes: ["pentagi-data:/data"] Defensive patterns
Strategy: fallback
Validate before calling
// server-side consistency check you can run as an operator
rows := db.Query("SELECT path, hash FROM user_resources WHERE hash <> ''")
for r := range rows {
if _, err := os.Stat(filepath.Join(dataDir, blobName(r.hash))); os.IsNotExist(err) {
log.Errorf("dangling resource %s: blob %s missing", r.path, r.hash)
}
} Try / catch
resp, _ := http.Get(downloadURL)
if isErrResourcesNotFound(resp) && strings.Contains(body, "not found on disk") {
// blob unrecoverable via API: restore from backup or re-upload
return reuploadAndRetry(path)
} Prevention
- Mount dataDir on a persistent Docker volume; never recreate it on deploy
- Backup DB and blob store together, atomically
- Exclude the blob directory from aggressive cleanup/GC scripts
- Alert on hash-vs-disk mismatches (dangling rows)
When it happens
Trigger: GET /resources/download where os.Open(BlobPath(dataDir, hash)) fails with ENOENT: the blob file was removed, the dataDir volume was recreated/mis-mounted, or deduplicated storage pruned a hash still referenced by a row.
Common situations: Docker volume for dataDir replaced or not mounted after redeploy; manual cleanup/gc scripts deleting blob files; backup restored only the DB without blob storage.
Related errors
- failed to commit blob
- failed to stat blob
- container path ' ' is not a directory
- failed to create directory
- failed to create file
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/4cecaa4adb43e377.
Report an issue: GitHub.
Appendix: source
Thrown at backend/pkg/server/services/resources.go:1882
response.Error(c, response.ErrResourcesNotFound, fmt.Errorf("resource %q not found", targetPath))
return
}
logger.FromContext(c).WithError(err).Error("error finding resource for download")
response.Error(c, response.ErrInternal, err)
return
}
entries = append(entries, resolvedEntry{rec: rec})
}
// Single regular file → serve as a direct attachment with explicit Content-Length.
if len(entries) == 1 && !entries[0].rec.IsDir {
e := entries[0]
blobPath := resources.BlobPath(s.dataDir, e.rec.Hash)
f, err := os.Open(blobPath)
if err != nil {
if os.IsNotExist(err) {
response.Error(c, response.ErrResourcesNotFound,
fmt.Errorf("blob for resource %q not found on disk", e.rec.Path))
return
}
logger.FromContext(c).WithError(err).Error("error opening resource blob for download")
response.Error(c, response.ErrInternal, err)
return
}
defer f.Close()
info, err := f.Stat()
if err != nil {
logger.FromContext(c).WithError(err).Error("error stating resource blob for download")
response.Error(c, response.ErrInternal, err)
return
}
c.DataFromReader(http.StatusOK, info.Size(), "application/octet-stream", f,
map[string]string{
"Content-Disposition": mime.FormatMediaType("attachment", map[string]string{
"filename": e.rec.Name,View on GitHub (pinned to ea665308ba)