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
- If the wrapped error is 'no such file or directory', create the domain config directory: os.MkdirAll(filepath.Dir(filename), 0755) before the WriteFile.
- 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.
- Check whether the config volume is mounted read-only and remount it rw; verify disk space.
- 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
- Create the domain config directory during domain provisioning, not lazily.
- Deploy with the config volume mounted read-write and correct UID mapping.
- Alert on disk-full; GetFooter provisioning is the first casualty.
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
- error reading footer config file: %v
- error unmarshalling footer config: %v
- error getting footer config: %v
- error saving footer config file: %v
- error saving default prompt config file: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/58acd3c908ebc025.
Report an issue: GitHub.