Billionmail/BillionMail · error
error saving default prompt config file: %v
Error message
error saving default prompt config file: %v
What it means
GetPrompt fails at project.go:795 when os.WriteFile cannot create prompt_config.json (mode 0644) in PRODUCT_CONFIG_PATH/<domain>/ while initializing the default prompt config for a domain that has no config file yet. os.WriteFile does not create parent directories, so a missing domain directory is the dominant cause; permission, read-only-filesystem, and disk-space problems produce the same error. The wrapped *fs.PathError identifies the exact path and errno.
Source
Thrown at core/internal/service/askai/project.go:795
filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/prompt_config.json", Domain)
if !public.FileExists(filename) {
// If the prompt config file does not exist, return a default prompt config
// This allows the system to handle cases where the prompt 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 prompt configuration without needing to handle file
// not found errors.
defaultPromptConfig := PromptConfig{
Prompt: "This is a default prompt. Please customize it.",
}
defaultPromptConfig.UpdateTime = public.GetNowTime()
defaultPromptJson, err := json.MarshalIndent(defaultPromptConfig, "", " ")
if err != nil {
return PromptConfig{}, fmt.Errorf("error marshalling default prompt config: %v", err)
}
err = os.WriteFile(filename, defaultPromptJson, 0644)
if err != nil {
return PromptConfig{}, fmt.Errorf("error saving default prompt config file: %v", err)
}
return defaultPromptConfig, nil
}
data, err := os.ReadFile(filename)
if err != nil {
return PromptConfig{}, fmt.Errorf("error reading prompt config file: %v", err)
}
var config PromptConfig
err = json.Unmarshal(data, &config)
if err != nil {
return PromptConfig{}, fmt.Errorf("error unmarshalling prompt config: %v", err)
}
return config, nil
}
// ModifyPrompt updates the prompt configuration for a given domain.
// It reads the existing prompt configuration, modifies the specified fields, and saves the updated configuration back to the file.View on GitHub (pinned to fc36c76c05)
Solutions
- Create the directory before writing: os.MkdirAll(filepath.Dir(filename), 0755) in the default-config branch.
- Fix permissions/ownership on PRODUCT_CONFIG_PATH so the service user can create files.
- Confirm the config volume is mounted read-write and has free space.
- Inspect the wrapped *fs.PathError errno (ENOENT/EACCES/ENOSPC/EROFS) to pick the right fix.
Example fix
// before
defaultPromptJson, err := json.MarshalIndent(defaultPromptConfig, "", " ")
if err != nil { ... }
err = os.WriteFile(filename, defaultPromptJson, 0644)
// after
defaultPromptJson, err := json.MarshalIndent(defaultPromptConfig, "", " ")
if err != nil { ... }
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return PromptConfig{}, fmt.Errorf("error creating config dir: %v", err)
}
if err := os.WriteFile(filename, defaultPromptJson, 0644); err != nil {
return PromptConfig{}, fmt.Errorf("error saving default prompt config file: %v", err)
} Defensive patterns
Strategy: validation
Validate before calling
filename := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/prompt_config.json", domain)
if err := os.MkdirAll(filepath.Dir(filename), 0755); err != nil {
return fmt.Errorf("cannot create config dir: %v", err)
}
probe := filepath.Join(filepath.Dir(filename), ".write_probe")
if err := os.WriteFile(probe, nil, 0644); err != nil {
return fmt.Errorf("dir not writable: %v", err)
}
os.Remove(probe) Type guard
func canCreateIn(dir string) bool {
probe := filepath.Join(dir, ".probe")
if err := os.WriteFile(probe, nil, 0644); err != nil {
return false
}
os.Remove(probe)
return true
} Try / catch
cfg, err := askai.GetPrompt(domain)
if err != nil {
var perr *fs.PathError
if errors.As(err, &perr) {
switch {
case errors.Is(perr.Err, fs.ErrNotExist):
log.Printf("config dir %s missing — run MkdirAll during domain setup", perr.Path)
case errors.Is(perr.Err, fs.ErrPermission):
log.Printf("permission denied on %s — fix volume ownership", perr.Path)
default:
log.Printf("default prompt write failed: %v", err)
}
}
return err
} Prevention
- Create the domain config directory at domain-onboarding time, not lazily on first read.
- Mount config volumes read-write and grant the service user ownership.
- Check disk space and mount flags (rw vs ro) after deployment.
- Treat the wrapped fs.Err errno (ENOENT/EACCES/ENOSPC/EROFS) to choose the fix.
When it happens
Trigger: First call to GetPrompt(domain) (or ModifyPrompt which calls it) for a domain whose directory PRODUCT_CONFIG_PATH/<domain> was never created, when the process lacks write permission on that path, or when the config volume is read-only/full.
Common situations: Newly onboarded domain where no other config file has been written yet (nothing created the domain dir); app running as unprivileged container user while config dir is root-owned; docker-compose config volume mounted read-only; host disk full.
Understand the failure class
Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.
Related errors
- error saving default footer config file: %v
- error saving footer config file: %v
- error reading prompt config file: %v
- project configuration file does not exist: %s
- error reading project configuration file: %v
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/44d749268566ef1f.
Report an issue: GitHub.