OpenNHP/opennhp · error

could not open file

Error message

could not open file: %v

What it means

ReadZdtoConfig could not open `etc/ztdo/data-<doId>.json`. The ZTDO config for the given data object ID does not exist or is unreadable, so the function returns an empty DRGMsg with this wrapped os.Open error. Callers (DAR/DAV handlers, SaveZdtoConfig's existence probe) treat a non-nil error as "no config yet".

Solutions

  1. Verify the doId in the request matches an existing etc/ztdo/data-<doId>.json on this server (ls the directory).
  2. Re-run the DHP DRG (publish) flow to create the missing ZTDO config before reading it.
  3. If configs must survive restarts, mount a persistent volume at ExeDirPath/etc/ztdo.
  4. Check file permissions on etc/ztdo and its contents for the server process user.

Example fix

// before: reading without checking existence
msg, err := ReadZdtoConfig(doId)
// after: probe first and handle the not-found path
if _, statErr := os.Stat(filepath.Join(ExeDirPath, "etc", "ztdo", "data-"+doId+".json")); os.IsNotExist(statErr) {
    return fmt.Errorf("ztdo %s not published on this server", doId)
}
msg, err := ReadZdtoConfig(doId)
Defensive patterns

Strategy: validation

Validate before calling

p := filepath.Join(exeDir, "etc", "ztdo", "data-"+doId+".json")
if _, err := os.Stat(p); os.IsNotExist(err) {
    return fmt.Errorf("ztdo %q not published on this server", doId)
}

Type guard

func ztdoConfigExists(doId string) bool {
    if doId == "" { return false }
    _, err := os.Stat(filepath.Join(ExeDirPath, "etc", "ztdo", "data-"+doId+".json"))
    return err == nil
}

Try / catch

msg, err := ReadZdtoConfig(doId)
if err != nil && strings.Contains(err.Error(), "could not open file") {
    // treat as unpublished: fall back to re-publish flow
    return republishZtdo(doId)
}

Prevention

When it happens

Trigger: HandleDHPDARMessage or HandleDHPDAVMessage references a doId whose config file was never saved on this server, or the file is unreadable due to permissions; also fires inside SaveZdtoConfig when probing whether a config already exists.

Common situations: Client asks for a ZTDO on the wrong server (config was written on another node); server restarted with an empty volume so etc/ztdo is wiped; typo'd doId in the request.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at endpoints/server/msghandler.go:755

	file, err := os.Create(configPath)
	if err != nil {
		return fmt.Errorf("failed to create config.json: %v", err)
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	return encoder.Encode(drgMsg)
}

// read data-<doId>.json to DRGMsg Object
func ReadZdtoConfig(doId string) (common.DRGMsg, error) {
	etcDir := filepath.Join(ExeDirPath, "etc", "ztdo")
	configFilePath := filepath.Join(etcDir, "data-"+doId+".json")
	file, err := os.Open(configFilePath)
	if err != nil {
		return common.DRGMsg{}, fmt.Errorf("could not open file: %v", err)
	}
	defer file.Close()

	fileContentByte, err := io.ReadAll(file)
	if err != nil {
		return common.DRGMsg{}, fmt.Errorf("error reading file: %v", err)
	}

	var config common.DRGMsg

	err = json.Unmarshal(fileContentByte, &config)
	if err != nil {
		return common.DRGMsg{}, fmt.Errorf("json parsing error: %s", err)
	}
	return config, nil
}

// HandleRelayForward processes a decrypted NHP_RLY message from the standard

View on GitHub (pinned to 6e04ca5ff0)