OpenNHP/opennhp · error
fail to read resource
Error message
fail to read resource: %w
What it means
After os.Stat succeeds, loadResource reads the file with os.ReadFile; any read failure is wrapped as 'fail to read resource: %w'. Stat succeeding but Read failing typically means the file changed between the two calls, or the open/read step hit a permission or I/O problem the stat did not catch. The underlying OS error is preserved for diagnosis.
Solutions
- Check the wrapped errno in the log: EISDIR means the path holds a directory — replace it with the actual resource file.
- Re-check and fix file read permissions (chmod a+r / chown to the daemon user).
- If the file disappeared mid-request, redeploy the resource and retry; consider reading once and caching.
- Verify the file is a regular file (add a Stat mode check before ReadFile).
- Check dmesg/system logs for disk I/O errors if errno is EIO.
Example fix
// before
if _, statErr := os.Stat(absFullPath); statErr != nil { ... }
data, err := os.ReadFile(absFullPath)
// after: validate it is a regular file before reading
fi, statErr := os.Stat(absFullPath)
if statErr != nil { ... }
if !fi.Mode().IsRegular() {
return nil, errors.New("resource not found")
}
data, err := os.ReadFile(absFullPath) Defensive patterns
Strategy: try-catch
Validate before calling
fi, err := os.Stat(absPath)
if err == nil && !fi.Mode().IsRegular() {
// path is not a regular file; read will fail
} Type guard
func isReadableRegularFile(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsRegular()
} Try / catch
data, err := GetResource(id, token)
if err != nil {
if errors.Is(err, os.ErrPermission) {
// fix file perms / return 403
} else if errors.Is(err, syscall.EISDIR) || errors.Is(err, syscall.ENOTDIR) {
// bad deployment: path holds wrong file type
}
} Prevention
- Deploy resources as regular files, never directories.
- Use atomic rename (write temp + rename) to avoid reads of half-deleted files.
- Set file mode 0644 at deploy time.
- Avoid concurrent deletion during serving; use versioned resource dirs.
- Monitor for disk errors if EIO appears repeatedly.
When it happens
Trigger: os.ReadFile(absFullPath) returns an error: file is a directory (stat follows symlinks but ReadFile opens), file removed between Stat and Read, read permission missing (EACCES on open), disk I/O error, or file is a special device that fails on read.
Common situations: A directory was placed at a resource path (stat succeeds on directories); resource file deleted concurrently by a redeploy while a request was in flight; permissions allow stat on directory but not read of the file; full disk or failing storage.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- fail to check resource
- resource not found
- error reading file
- keystore: create directory
- error reading file
AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07).
Data as JSON: /api/errors/5bb13978ab6d43b4.
Report an issue: GitHub.
Appendix: source
Thrown at endpoints/server/kbs/resource/resource.go:185
if err != nil {
return nil, fmt.Errorf("fail to get resource absolute path: %w", err)
}
// Check if the path is within the base directory to avoid path traversal attack.
if !strings.HasPrefix(absFullPath, absBaseDir) {
return nil, errors.New("invalid resource ID: potential path traversal attack")
}
if _, statErr := os.Stat(absFullPath); statErr != nil {
if os.IsNotExist(statErr) {
return nil, errors.New("resource not found")
}
return nil, fmt.Errorf("fail to check resource: %w", statErr)
}
data, err := os.ReadFile(absFullPath)
if err != nil {
return nil, fmt.Errorf("fail to read resource: %w", err)
}
return data, nil
}
func encryptWithA256GCM(key, plaintext []byte) (ciphertext, iv, tag []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, nil, nil, err
}
iv = make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, nil, nil, errView on GitHub (pinned to 6e04ca5ff0)