Billionmail/BillionMail · error

error reading site map file: %v

Error message

error reading site map file: %v

What it means

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.

Source

Thrown at core/internal/service/askai/project.go:620

	if !public.FileExists(filename) {
		// If the site map file does not exist, return an empty slice
		// This allows the system to handle cases where the site map has not been set up
		// and avoids errors when trying to read a non-existent file.
		// It also allows the user to create a new site map without needing to handle file not found errors.
		emptySiteMap := []SiteMap{}
		emptySiteMapJson, err := json.MarshalIndent(emptySiteMap, "", "  ")
		if err != nil {
			return nil, fmt.Errorf("error marshalling empty site map: %v", err)
		}
		err = os.WriteFile(filename, emptySiteMapJson, 0644)
		if err != nil {
			return nil, fmt.Errorf("error saving empty site map file: %v", err)
		}
		return emptySiteMap, nil
	}
	data, err := os.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("error reading site map file: %v", err)
	}

	var siteMap []SiteMap
	err = json.Unmarshal(data, &siteMap)
	if err != nil {
		return nil, fmt.Errorf("error unmarshalling site map: %v", err)
	}
	return siteMap, nil
}

func SaveSiteMap(Domain string, siteMap []SiteMap) error {
	filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/sitemap.json", Domain)
	data, err := json.MarshalIndent(siteMap, "", "  ")
	if err != nil {
		return fmt.Errorf("error marshalling site map: %v", err)
	}
	err = os.WriteFile(filename, data, 0644)
	if err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. 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.
  2. Restore read permissions on sitemap.json for the service user (chmod 644, chown to service user).
  3. Verify the path is a regular file, not a directory: ls -la PRODUCT_CONFIG_PATH/<domain>/sitemap.json.
  4. Retry the read once on transient failure if a concurrent writer is expected.

Example fix

// before
if !public.FileExists(filename) { /* bootstrap empty */ }
data, err := os.ReadFile(filename)
if err != nil { return nil, fmt.Errorf("error reading site map file: %v", err) }
// after
data, err := os.ReadFile(filename)
if err != nil {
    if errors.Is(err, os.ErrNotExist) {
        return bootstrapEmptySiteMap(filename) // single code path, no TOCTOU race
    }
    return nil, fmt.Errorf("error reading site map file: %v", err)
}
Defensive patterns

Strategy: fallback

Validate before calling

func readableFile(path string) bool {
    f, err := os.Open(path)
    if err != nil { return false }
    f.Close()
    return true
}
// check just before reading; but prefer removing the check-and-read race entirely

Try / catch

m, err := GetSiteMap(domain)
if err != nil && strings.Contains(err.Error(), "error reading site map file") {
    // transient (e.g. concurrent delete): fall back to empty map and re-bootstrap
    return []SiteMap{}, nil
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/09f7019a841c1b1a. Report an issue: GitHub.