Billionmail/BillionMail · error

error saving default footer config file: %v

Error message

error saving default footer config file: %v

What it means

When footer_config.json is missing, GetFooter writes a freshly marshalled default config to PRODUCT_CONFIG_PATH/<Domain>/footer_config.json via os.WriteFile (0644) so subsequent reads succeed. This error wraps any filesystem failure during that provisioning write: missing domain directory, permissions, read-only filesystem, or disk full.

Source

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

	filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/footer_config.json", Domain)
	if !public.FileExists(filename) {
		// If the footer config file does not exist, return a default footer config
		// This allows the system to handle cases where the footer configuration 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 footer configuration without needing to handle file
		// not found errors.
		defaultFooterConfig := FooterConfig{
			CopyrightText: "© 2023 Your Company. All rights reserved.",
			Disclaimer:    "This is a sample disclaimer.",
		}
		defaultFooterConfig.UpdateTime = public.GetNowTime()
		defaultFooterJson, err := json.MarshalIndent(defaultFooterConfig, "", "  ")
		if err != nil {
			return FooterConfig{}, fmt.Errorf("error marshalling default footer config: %v", err)
		}
		err = os.WriteFile(filename, defaultFooterJson, 0644)
		if err != nil {
			return FooterConfig{}, fmt.Errorf("error saving default footer config file: %v", err)
		}
		// Return the default footer config if the file does not exist
		return defaultFooterConfig, nil
	}
	data, err := os.ReadFile(filename)
	if err != nil {
		return FooterConfig{}, fmt.Errorf("error reading footer config file: %v", err)
	}

	var config FooterConfig
	err = json.Unmarshal(data, &config)
	if err != nil {
		return FooterConfig{}, fmt.Errorf("error unmarshalling footer config: %v", err)
	}
	return config, nil
}

// ModifyFooter updates the footer configuration for a given domain.

View on GitHub (pinned to fc36c76c05)

Solutions

  1. If the wrapped error is 'no such file or directory', create the domain config directory: os.MkdirAll(filepath.Dir(filename), 0755) before the WriteFile.
  2. If 'permission denied', chown/chmod PRODUCT_CONFIG_PATH/<Domain> so the running user can write, or align the container's UID with the volume owner.
  3. Check whether the config volume is mounted read-only and remount it rw; verify disk space.
  4. Ensure PRODUCT_CONFIG_PATH resolves to the persistent volume in the deployed environment, not an ephemeral image path.

Example fix

// before
err = os.WriteFile(filename, defaultFooterJson, 0644)
// after
if mkErr := os.MkdirAll(filepath.Dir(filename), 0755); mkErr != nil {
    return FooterConfig{}, fmt.Errorf("error creating footer config directory: %v", mkErr)
}
err = os.WriteFile(filename, defaultFooterJson, 0644)
Defensive patterns

Strategy: validation

Validate before calling

dir := filepath.Join(configPath, domain)
if err := os.MkdirAll(dir, 0755); err != nil { return err }
if f, err := os.OpenFile(filepath.Join(dir, ".probe"), os.O_CREATE|os.O_WRONLY, 0644); err != nil { return err } else { f.Close(); os.Remove(filepath.Join(dir, ".probe")) }

Type guard

func canProvisionDefault(domain string) bool {
    return writableDir(filepath.Join(configPath, domain))
}

Try / catch

if _, err := askai.GetFooter(domain); err != nil {
    if strings.Contains(err.Error(), "saving default footer config") {
        // provisioning failed: create dir / fix perms / remount rw, then retry
    } else { return err }
}

Prevention

When it happens

Trigger: First GetFooter call for a domain whose PRODUCT_CONFIG_PATH/<Domain>/ directory does not exist or is not writable by the service user; also triggered by ModifyFooter and GetFooterPrompt as they route through GetFooter.

Common situations: New domain provisioned in the app but its config directory never created in the container/volume; config volume mounted read-only in production; service runs as non-root but the config dir was created by root with 0755 root-owned perms; disk-full on the host.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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