Billionmail/BillionMail · error

error saving site map file: %v

Error message

error saving site map file: %v

What it means

SaveSiteMap writes the marshalled sitemap to PRODUCT_CONFIG_PATH/<domain>/sitemap.json via os.WriteFile with 0644. This error wraps any write failure: the domain config directory missing (SaveSiteMap never creates it), permission denial, read-only filesystem, or disk-full.

Source

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

	}

	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 {
		return fmt.Errorf("error saving site map file: %v", err)
	}
	return nil
}

// AddSiteMapNode adds a new site map node with the provided title and URI path to the site map of the specified domain.
// It reads the existing site map, appends the new node, and saves the updated site map back to the file.
// If the site map file does not exist or cannot be read, it returns an error.
func AddSiteMapNode(Domain string, Title string, UriPath string) error {
	siteMap, err := GetSiteMap(Domain)
	if err != nil {
		return fmt.Errorf("error getting site map: %v", err)
	}

	newNode := SiteMap{
		Title:      Title,
		UriPath:    UriPath,
		UpdateTime: public.GetNowTime(),
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Create the directory before writing: os.MkdirAll(filepath.Dir(filename), 0755) in SaveSiteMap or at domain provisioning.
  2. Check ownership/permissions of PRODUCT_CONFIG_PATH/<domain>/ (chown/chmod for the service user).
  3. Confirm the nested os error text to distinguish 'no such file or directory' from 'permission denied' or 'no space left on device'.
  4. Free disk space or remount the volume read-write if the cause is disk/quota related.

Example fix

// before
filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/sitemap.json", Domain)
data, _ := json.MarshalIndent(siteMap, "", "  ")
err := os.WriteFile(filename, data, 0644)
// after
filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/sitemap.json", Domain)
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
    return fmt.Errorf("error creating sitemap dir: %v", err)
}
data, _ := json.MarshalIndent(siteMap, "", "  ")
err := os.WriteFile(filename, data, 0644)
Defensive patterns

Strategy: try-catch

Validate before calling

func ensureSitemapDir(domain string) error {
    dir := filepath.Join(PRODUCT_CONFIG_PATH, domain)
    if err := os.MkdirAll(dir, 0755); err != nil { return err }
    if info, err := os.Stat(dir); err != nil || !info.IsDir() {
        return fmt.Errorf("%s not writable dir", dir)
    }
    return nil
}

Try / catch

if err := SaveSiteMap(domain, siteMap); err != nil {
    switch {
    case errors.Is(err, os.ErrNotExist):
        os.MkdirAll(filepath.Join(PRODUCT_CONFIG_PATH, domain), 0755)
        err = SaveSiteMap(domain, siteMap) // retry once after mkdir
    case errors.Is(err, os.ErrPermission):
        log.Printf("permission denied writing sitemap for %s", domain)
    }
    return err
}

Prevention

When it happens

Trigger: Calling SaveSiteMap (or AppendSitemap) when PRODUCT_CONFIG_PATH/<domain>/ does not exist (ENOENT), when the process cannot write there (EACCES/EPERM), or on a full/read-only filesystem.

Common situations: Adding a sitemap node for a domain whose config directory was never provisioned; running under a different user after a migration; Docker volume mounted read-only; disk quota exceeded.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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