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
- Verify the doId in the request matches an existing etc/ztdo/data-<doId>.json on this server (ls the directory).
- Re-run the DHP DRG (publish) flow to create the missing ZTDO config before reading it.
- If configs must survive restarts, mount a persistent volume at ExeDirPath/etc/ztdo.
- 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
- Verify the doId is routed to the server that holds its config.
- Mount etc/ztdo on persistent storage so restarts don't lose configs.
- Publish (DRG) before attempting DAR/DAV reads for a new ztdo.
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
- could not open file
- already exists, please delete it first
- failed to create config.json
- error reading file
- file path cannot be empty
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 standardView on GitHub (pinned to 6e04ca5ff0)