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

  1. Check the wrapped errno in the log: EISDIR means the path holds a directory — replace it with the actual resource file.
  2. Re-check and fix file read permissions (chmod a+r / chown to the daemon user).
  3. If the file disappeared mid-request, redeploy the resource and retry; consider reading once and caching.
  4. Verify the file is a regular file (add a Stat mode check before ReadFile).
  5. 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

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


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, err

View on GitHub (pinned to 6e04ca5ff0)