{"record":{"id":"ed97947c5cdaa66d","repo":"vxcontrol/pentagi","slug":"failed-to-delete-blob-s-w","errorCode":null,"errorMessage":"failed to delete blob %s: %w","messagePattern":"failed to delete blob (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/resources/resources.go","lineNumber":124,"sourceCode":"\t_, err := os.Lstat(BlobPath(dataDir, hash))\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\n// DeleteBlob removes the .blob file for hash. It is safe to call if the file\n// does not exist (returns nil in that case).\nfunc DeleteBlob(dataDir, hash string) error {\n\tif err := validateBlobHash(hash); err != nil {\n\t\treturn err\n\t}\n\terr := os.Remove(BlobPath(dataDir, hash))\n\tif err != nil && !os.IsNotExist(err) {\n\t\treturn fmt.Errorf(\"failed to delete blob %s: %w\", hash, err)\n\t}\n\treturn nil\n}\n\n// SanitizeResourcePath normalises a client-supplied virtual path and ensures it\n// is safe to use:\n//   - trims whitespace\n//   - converts backslashes to forward slashes\n//   - cleans the path (removes .., double slashes, etc.)\n//   - rejects absolute paths, dot-only components, and paths that exceed MaxPathLength\n//   - returns an error for the empty path\nfunc SanitizeResourcePath(p string) (string, error) {\n\ttrimmed := strings.TrimSpace(p)\n\tif trimmed == \"\" {\n\t\treturn \"\", fmt.Errorf(\"path must not be empty\")\n\t}\n\tif len(trimmed) > MaxPathLength {\n\t\treturn \"\", fmt.Errorf(\"path exceeds maximum allowed length of %d characters\", MaxPathLength)","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/resources/resources.go#L106-L142","documentation":"DeleteBlob (resources.go:124) removes the .blob file for a hash after validating it. If os.Remove fails for any reason other than 'file does not exist' (which is silently treated as success), the error 'failed to delete blob %s: %w' wraps the filesystem error. So this surfaces real removal problems: permissions, directory issues, or the path being a non-empty directory.","triggerScenarios":"os.Remove returns EACCES/EPERM (no write permission on the blob directory), the blob path exists as a directory, the filesystem is read-only or full (rare for unlink), or the dataDir points to the wrong location holding a similarly-named directory.","commonSituations":"Running the process as a non-root user after blobs were created by another user; mounting the blob volume read-only; orphan-blob cleanup workers (deleteOrphanBlobsIfUnreferenced, cleanupOrphanBlobs) hitting blobs locked down by backup tooling; k8s volumes remounted read-only.","solutions":["Check the wrapped cause (errors.Unwrap / os.RemoveAll behavior) and the path from BlobPath(dataDir, hash)","Verify write permission on the blob directory for the process user (ls -ld dataDir)","Confirm the volume is not mounted read-only (mount | grep, kubectl describe)","If the entry is a directory or corrupted, remove it manually or with os.RemoveAll after verifying it is safe"],"exampleFix":"// before\nif err := resources.DeleteBlob(dataDir, hash); err != nil { return err }\n// after\nif err := resources.DeleteBlob(dataDir, hash); err != nil {\n    var perr *fs.PathError\n    if errors.As(err, &perr) && (errors.Is(perr, os.ErrPermission) || errors.Is(perr, syscall.EACCES)) {\n        log.Warn(\"skipping blob delete, permission denied\", \"hash\", hash)\n        return nil // or escalate\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"if !resources.IsValidBlobHash(hash) { return fmt.Errorf(\"invalid hash %q\", hash) }\nif info, err := os.Stat(resources.BlobPath(dataDir, hash)); err == nil && info.IsDir() {\n    return fmt.Errorf(\"blob path is a directory\")\n}","typeGuard":"func isPermissionErr(err error) bool {\n    var perr *fs.PathError\n    return errors.As(err, &perr) && errors.Is(perr, os.ErrPermission)\n}","tryCatchPattern":"if err := resources.DeleteBlob(dataDir, hash); err != nil {\n    if isPermissionErr(err) { log.Warn(\"no permission to delete blob\", \"hash\", hash); return nil }\n    return fmt.Errorf(\"blob delete failed: %w\", err)\n}","preventionTips":["Run the service with a user that owns or has write access to the blob directory","Ensure blob volumes are mounted read-write (verify in docker-compose/k8s specs)","Treat os.ErrNotExist as success — DeleteBlob already does — so only handle real failures","Check disk/volume health in cleanup workers and log the wrapped PathError for diagnosis"],"tags":["filesystem","delete","permissions"],"backgroundTag":"file-delete-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}