OpenNHP/opennhp · error

fail to check resource

Error message

fail to check resource: %w

What it means

loadResource wraps any os.Stat error on the resolved resource path that is NOT a 'does not exist' error with 'fail to check resource: %w'. It exists so that genuinely missing files return the distinct 'resource not found' error while permission, I/O, or path-form problems surface with their underlying cause preserved via %w. Callers (GetResource) use this to map failures onto HTTP responses.

Solutions

  1. Check permissions on the resource directory tree with `ls -la` and `sudo -u <serveruser> stat <path>`; fix with chmod/chown so the server user can traverse it.
  2. Inspect the wrapped cause (%w) in the log to see the exact errno and address it (ENOTDIR, EACCES, EIO, etc.).
  3. Verify baseDir configuration points at a directory that exists and is readable by the daemon.
  4. If on a network mount, check mount health and remount; retry the request.
  5. If the resource is genuinely gone, redeploy the resource file under baseDir.

Example fix

// before: any stat failure surfaces as opaque 500
if _, statErr := os.Stat(absFullPath); statErr != nil {
	return nil, fmt.Errorf("fail to check resource: %w", statErr)
}
// after: handle not-a-directory like not-found for clearer client errors
if _, statErr := os.Stat(absFullPath); statErr != nil {
	if os.IsNotExist(statErr) || errors.Is(statErr, syscall.ENOTDIR) {
		return nil, errors.New("resource not found")
	}
	return nil, fmt.Errorf("fail to check resource: %w", statErr)
}
Defensive patterns

Strategy: try-catch

Validate before calling

path := filepath.Join(baseDir, resourceID)
abs, _ := filepath.Abs(path)
if _, err := os.Stat(abs); err != nil {
	if !os.IsNotExist(err) {
		// pre-check: surface stat problem early
		log.Printf("resource path unusable: %v", err)
	}
}

Type guard

func isStatPermissionErr(err error) bool {
	return errors.Is(err, os.ErrPermission)
}

Try / catch

data, err := GetResource(id, token)
if err != nil {
	var statErr *os.PathError
	if errors.As(err, &statErr) && !os.IsNotExist(statErr) {
		// permissions/I-O issue: alert operator, return 500
	}
}

Prevention

When it happens

Trigger: os.Stat(absFullPath) fails with an error other than ENOENT — e.g. a permission error (EACCES) on the resource directory, the path is not reachable, an I/O error occurs, or a component of the path is not a directory (ENOTDIR). Called via GetResource when serving KBS resource requests.

Common situations: Resource files deployed with wrong ownership/permissions so the nhp-server process cannot stat them; the resource path contains a regular file used as a directory component; NFS/network mounts flaking; baseDir misconfigured to point into a restricted directory.

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


AI-assisted analysis of OpenNHP/opennhp@6e04ca5ff0 (2026-09-07). Data as JSON: /api/errors/823b5096d337be28. Report an issue: GitHub.

Appendix: source

Thrown at endpoints/server/kbs/resource/resource.go:180

	}

	fullPath := filepath.Join(absBaseDir, resourceID)

	absFullPath, err := filepath.Abs(fullPath)
	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

View on GitHub (pinned to 6e04ca5ff0)