{"record":{"id":"09f7019a841c1b1a","repo":"Billionmail/BillionMail","slug":"error-reading-site-map-file-v","errorCode":null,"errorMessage":"error reading site map file: %v","messagePattern":"error reading site map file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":620,"sourceCode":"\tif !public.FileExists(filename) {\n\t\t// If the site map file does not exist, return an empty slice\n\t\t// This allows the system to handle cases where the site map has not been set up\n\t\t// and avoids errors when trying to read a non-existent file.\n\t\t// It also allows the user to create a new site map without needing to handle file not found errors.\n\t\temptySiteMap := []SiteMap{}\n\t\temptySiteMapJson, err := json.MarshalIndent(emptySiteMap, \"\", \"  \")\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error marshalling empty site map: %v\", err)\n\t\t}\n\t\terr = os.WriteFile(filename, emptySiteMapJson, 0644)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error saving empty site map file: %v\", err)\n\t\t}\n\t\treturn emptySiteMap, nil\n\t}\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading site map file: %v\", err)\n\t}\n\n\tvar siteMap []SiteMap\n\terr = json.Unmarshal(data, &siteMap)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error unmarshalling site map: %v\", err)\n\t}\n\treturn siteMap, nil\n}\n\nfunc SaveSiteMap(Domain string, siteMap []SiteMap) error {\n\tfilename := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s/sitemap.json\", Domain)\n\tdata, err := json.MarshalIndent(siteMap, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling site map: %v\", err)\n\t}\n\terr = os.WriteFile(filename, data, 0644)\n\tif err != nil {","sourceCodeStart":602,"sourceCodeEnd":638,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L602-L638","documentation":"GetSiteMap reads PRODUCT_CONFIG_PATH/<domain>/sitemap.json with os.ReadFile after confirming the file exists via public.FileExists. This error wraps any read failure — most often a TOCTOU race where the file is deleted between the existence check and the read, or permission problems on the file itself.","triggerScenarios":"Concurrent deletion/removal of sitemap.json between FileExists and os.ReadFile (race with RemoveSiteMapNode or external cleanup); read permission removed from the file; filename resolves to a directory.","commonSituations":"Two requests hitting the sitemap concurrently (one recreating/normalizing the file while another reads); ops scripts pruning config files while the service runs; misconfigured permissions after a restore/migration.","solutions":["Drop the FileExists pre-check and use os.ReadFile directly with errors.Is(err, os.ErrNotExist) to return an empty map instead — this removes the race window.","Restore read permissions on sitemap.json for the service user (chmod 644, chown to service user).","Verify the path is a regular file, not a directory: ls -la PRODUCT_CONFIG_PATH/<domain>/sitemap.json.","Retry the read once on transient failure if a concurrent writer is expected."],"exampleFix":"// before\nif !public.FileExists(filename) { /* bootstrap empty */ }\ndata, err := os.ReadFile(filename)\nif err != nil { return nil, fmt.Errorf(\"error reading site map file: %v\", err) }\n// after\ndata, err := os.ReadFile(filename)\nif err != nil {\n    if errors.Is(err, os.ErrNotExist) {\n        return bootstrapEmptySiteMap(filename) // single code path, no TOCTOU race\n    }\n    return nil, fmt.Errorf(\"error reading site map file: %v\", err)\n}","handlingStrategy":"fallback","validationCode":"func readableFile(path string) bool {\n    f, err := os.Open(path)\n    if err != nil { return false }\n    f.Close()\n    return true\n}\n// check just before reading; but prefer removing the check-and-read race entirely","typeGuard":null,"tryCatchPattern":"m, err := GetSiteMap(domain)\nif err != nil && strings.Contains(err.Error(), \"error reading site map file\") {\n    // transient (e.g. concurrent delete): fall back to empty map and re-bootstrap\n    return []SiteMap{}, nil\n}","preventionTips":["Replace FileExists + ReadFile with a direct os.ReadFile and handle os.ErrNotExist — removes the TOCTOU race.","Avoid external scripts deleting files under PRODUCT_CONFIG_PATH while the service runs.","Restore uniform ownership/permissions (644 files, 755 dirs, service user) after migrations."],"tags":["go","file-io","race-condition","permissions"],"backgroundTag":"file-read-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}