OpenNHP/opennhp · error

already exists, please delete it first

Error message

%v already exists, please delete it first

What it means

SaveZdtoConfig found that `etc/ztdo/data-<doId>.json` still exists after the attempted removal, so writing would overwrite an existing ZTDO config. It refuses with "... already exists, please delete it first" rather than silently clobbering the config.

Solutions

  1. Delete etc/ztdo/data-<doId>.json manually and retry the DRG flow.
  2. Avoid sending concurrent DHP DRG messages for the same doId, or serialize them on the server.
  3. Check file permissions — if os.Remove failed silently due to permissions, fix directory writability first.
  4. Re-run after ensuring only one writer touches the ztdo directory.

Example fix

// before: race leaves the file behind
// two HandleDHPDRGMessage calls for doId X run concurrently
// after: serialize per-doId saves
var ztdoMu sync.Mutex
func SaveZdtoConfig(d *common.DRGMsg) error { ztdoMu.Lock(); defer ztdoMu.Unlock(); return saveZdtoLocked(d) }
Defensive patterns

Strategy: try-catch

Validate before calling

p := filepath.Join(exeDir, "etc", "ztdo", "data-"+doId+".json")
if _, err := os.Stat(p); err == nil {
    os.Remove(p) // deliberate replace: remove before save
}

Type guard

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

Try / catch

if err := SaveZdtoConfig(drg); err != nil && strings.Contains(err.Error(), "already exists") {
    os.Remove(filepath.Join(ExeDirPath, "etc", "ztdo", "data-"+drg.DoId+".json"))
    err = SaveZdtoConfig(drg)
}

Prevention

When it happens

Trigger: HandleDHPDRGMessage calls SaveZdtoConfig for a doId whose config file exists but could not be read by ReadZdtoConfig (so the merge/remove branch at line 718 was skipped), yet os.Remove in the earlier branch didn't delete it — typically because the file was recreated between the ReadZdtoConfig failure and the os.Stat check, or the remove failed silently while the stat still sees it.

Common situations: Concurrent DHP DRG messages for the same doId arriving in parallel: one instance recreates the file after the other's remove; a corrupted data-<doId>.json that fails ReadZdtoConfig's JSON parse also blocks the remove-and-merge path under races.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


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

Appendix: source

Thrown at endpoints/server/msghandler.go:735

	if existingDrgMsg, err := ReadZdtoConfig(objectId); err == nil {
		// alway keep original date source type
		drgMsg.DataSourceType = existingDrgMsg.DataSourceType

		if drgMsg.AccessUrl == "" { // provider update access url
			drgMsg.AccessUrl = existingDrgMsg.AccessUrl
		}

		os.Remove(configPath)
	}

	// Make sure the etc directory exists
	if err := os.MkdirAll(etcDir, 0755); err != nil {
		return fmt.Errorf("failed to create etc directory: %v", err)
	}

	if _, err := os.Stat(configPath); err == nil {
		return fmt.Errorf("%v already exists, please delete it first", configFileName)
	}

	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)

View on GitHub (pinned to 6e04ca5ff0)