{"record":{"id":"064f783f2d1da7d5","repo":"OpenNHP/opennhp","slug":"v-already-exists-please-delete-it-first-064f78","errorCode":null,"errorMessage":"%v already exists, please delete it first","messagePattern":"(.+?) already exists, please delete it first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"endpoints/server/msghandler.go","lineNumber":735,"sourceCode":"\n\tif existingDrgMsg, err := ReadZdtoConfig(objectId); err == nil {\n\t\t// alway keep original date source type\n\t\tdrgMsg.DataSourceType = existingDrgMsg.DataSourceType\n\n\t\tif drgMsg.AccessUrl == \"\" { // provider update access url\n\t\t\tdrgMsg.AccessUrl = existingDrgMsg.AccessUrl\n\t\t}\n\n\t\tos.Remove(configPath)\n\t}\n\n\t// Make sure the etc directory exists\n\tif err := os.MkdirAll(etcDir, 0755); err != nil {\n\t\treturn fmt.Errorf(\"failed to create etc directory: %v\", err)\n\t}\n\n\tif _, err := os.Stat(configPath); err == nil {\n\t\treturn fmt.Errorf(\"%v already exists, please delete it first\", configFileName)\n\t}\n\n\tfile, err := os.Create(configPath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create config.json: %v\", err)\n\t}\n\tdefer file.Close()\n\n\tencoder := json.NewEncoder(file)\n\tencoder.SetIndent(\"\", \"  \")\n\treturn encoder.Encode(drgMsg)\n}\n\n// read data-<doId>.json to DRGMsg Object\nfunc ReadZdtoConfig(doId string) (common.DRGMsg, error) {\n\tetcDir := filepath.Join(ExeDirPath, \"etc\", \"ztdo\")\n\tconfigFilePath := filepath.Join(etcDir, \"data-\"+doId+\".json\")\n\tfile, err := os.Open(configFilePath)","sourceCodeStart":717,"sourceCodeEnd":753,"githubUrl":"https://github.com/OpenNHP/opennhp/blob/6e04ca5ff03222a699c24205cd4bf8fee9af7ffe/endpoints/server/msghandler.go#L717-L753","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Delete etc/ztdo/data-<doId>.json manually and retry the DRG flow.","Avoid sending concurrent DHP DRG messages for the same doId, or serialize them on the server.","Check file permissions — if os.Remove failed silently due to permissions, fix directory writability first.","Re-run after ensuring only one writer touches the ztdo directory."],"exampleFix":"// before: race leaves the file behind\n// two HandleDHPDRGMessage calls for doId X run concurrently\n// after: serialize per-doId saves\nvar ztdoMu sync.Mutex\nfunc SaveZdtoConfig(d *common.DRGMsg) error { ztdoMu.Lock(); defer ztdoMu.Unlock(); return saveZdtoLocked(d) }","handlingStrategy":"try-catch","validationCode":"p := filepath.Join(exeDir, \"etc\", \"ztdo\", \"data-\"+doId+\".json\")\nif _, err := os.Stat(p); err == nil {\n    os.Remove(p) // deliberate replace: remove before save\n}","typeGuard":"func ztdoExists(doId string) bool {\n    _, err := os.Stat(filepath.Join(ExeDirPath, \"etc\", \"ztdo\", \"data-\"+doId+\".json\"))\n    return err == nil\n}","tryCatchPattern":"if err := SaveZdtoConfig(drg); err != nil && strings.Contains(err.Error(), \"already exists\") {\n    os.Remove(filepath.Join(ExeDirPath, \"etc\", \"ztdo\", \"data-\"+drg.DoId+\".json\"))\n    err = SaveZdtoConfig(drg)\n}","preventionTips":["Serialize saves per doId (mutex or single-writer queue) to avoid remove/create races.","Never hand-edit data-*.json while the server is running.","Clean stale configs during maintenance windows instead of relying on overwrite."],"tags":["filesystem","config","race-condition"],"backgroundTag":"file-already-exists","analyzedSha":"6e04ca5ff03222a699c24205cd4bf8fee9af7ffe","analyzedAt":"2026-09-07T15:44:59.941Z","contentChangedAt":"2026-09-07T15:44:59.941Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}