Billionmail/BillionMail · error
error reading footer config file: %v
Error message
error reading footer config file: %v
What it means
When footer_config.json exists, GetFooter reads it with os.ReadFile and wraps any read failure. Unlike the missing-file case (which auto-provisions a default), this fires when the file exists but cannot be read — typically permission problems, the path being a directory, or an I/O error occurring between the FileExists check and the read (race/deleted file, broken symlink).
Source
Thrown at core/internal/service/askai/project.go:729
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.
// It reads the existing footer configuration, modifies the specified fields, and saves the updated configuration back to the file.
// If any field is empty, it will not be updated.
func ModifyFooter(Domain string, CopyrightText, Disclaimer string, Text string) error {
config, err := GetFooter(Domain)
if err != nil {
return fmt.Errorf("error getting footer config: %v", err)
}View on GitHub (pinned to fc36c76c05)
Solutions
- Check the wrapped *PathError: 'permission denied' → fix ownership (chown service-user) and mode (chmod 0644) of footer_config.json.
- Verify the path is a regular file (ls -la); if it is a directory or symlink, replace it with a valid config file.
- If 'no such file or directory' appears despite existence, the file vanished between check and read — replace the FileExists+ReadFile pattern with a single ReadFile and treat os.IsNotExist as the default-config path.
- Ensure no external process (backup/AV/sync) is replacing or locking the file during reads.
Example fix
// before
if !public.FileExists(filename) { /* default */ }
data, err := os.ReadFile(filename)
// after
data, err := os.ReadFile(filename)
if err != nil {
if os.IsNotExist(err) { /* provision default */ }
return FooterConfig{}, fmt.Errorf("error reading footer config file: %v", err)
} Defensive patterns
Strategy: type-guard
Validate before calling
fi, err := os.Stat(footerPath)
if err != nil || !fi.Mode().IsRegular() {
return fmt.Errorf("footer_config.json is not a readable regular file")
}
if f, err := os.Open(footerPath); err != nil { return err } else { f.Close() } Type guard
func readableRegularFile(path string) bool {
fi, err := os.Stat(path)
if err != nil || !fi.Mode().IsRegular() { return false }
f, err := os.Open(path)
if err != nil { return false }
f.Close()
return true
} Try / catch
if _, err := askai.GetFooter(domain); err != nil {
if errors.Is(err, fs.ErrPermission) { /* chown/chmod file */ }
if errors.Is(err, fs.ErrNotExist) { /* raced delete: recreate or retry */ }
return err
} Prevention
- Deploy config files with service-user ownership and 0644.
- Never name directories or symlinks like config filenames.
- Prefer single os.ReadFile + os.IsNotExist handling over FileExists+Read (avoids TOCTOU races).
When it happens
Trigger: Calling GetFooter (or ModifyFooter/GetFooterPrompt/AutoGetProjectInfo that call it) when footer_config.json exists but is unreadable: owned by another user with restrictive modes, path is a directory named footer_config.json, dangling symlink, or the file is deleted between the FileExists check and ReadFile.
Common situations: Operator copied footer_config.json in as root with 0600 while the app runs unprivileged; a mount point or directory accidentally named footer_config.json; backup/sync tool replaced the file with a broken symlink.
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 saving default footer config file: %v
- error saving empty site map file: %v
- error saving site map file: %v
- error getting footer config: %v
- error reading project configuration file: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/9e36c6d2329f2eba.
Report an issue: GitHub.