Billionmail/BillionMail · critical

error writing project configuration file: %v

Error message

error writing project configuration file: %v

What it means

After successful marshalling, SaveProjectConfig writes the bytes to PRODUCT_CONFIG_PATH/<Domain>/project.json via os.WriteFile; any filesystem write failure is wrapped as this error, meaning the config was NOT persisted.

Source

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

// SaveProjectConfig saves the provided ProjectConfig to a JSON file based on the domain.
// It creates the directory if it does not exist and writes the configuration to project.json.
// If the file cannot be written, it returns an error.
// If the directory does not exist, it creates it with appropriate permissions.
func SaveProjectConfig(Domain string, config ProjectConfig) error {
	projectConfigPath := fmt.Sprintf("%s/%s", PRODUCT_CONFIG_PATH, Domain)
	if !public.FileExists(projectConfigPath) {
		os.MkdirAll(projectConfigPath, os.ModePerm)
	}
	filename := fmt.Sprintf("%s/project.json", projectConfigPath)

	configStr, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return fmt.Errorf("error marshalling project configuration: %v", err)
	}
	config.UpdateTime = public.GetNowTime() // Update the time before saving
	err = os.WriteFile(filename, configStr, os.ModePerm)
	if err != nil {
		return fmt.Errorf("error writing project configuration file: %v", err)
	}
	return nil
}

// Create initializes a new project configuration with the provided domain and URLs.
// It sets default values for other fields and saves the configuration to a file.
func Create(Domain string, urls []string) error {
	urlsCount := len(urls)
	if urls == nil || urlsCount == 0 {
		urls = append(urls, "http://"+Domain) // Default URL if none provided
	}
	if urlsCount > 3 {
		return fmt.Errorf("Add up to 3 URLs")
	}
	// Ensure all URLs start with "http://"
	for i, urlStr := range urls {
		if !strings.HasPrefix(urlStr, "http") {
			urls[i] = "http://" + urlStr

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check disk space (df -h) and volume writability for PRODUCT_CONFIG_PATH
  2. Verify the process user has write permission on the project directory
  3. Ensure the os.MkdirAll error is checked before WriteFile — don't ignore it
  4. In deployment, mount the config directory as read-write and correct ownership

Example fix

// before
os.MkdirAll(projectConfigPath, os.ModePerm) // error ignored
// after
if err := os.MkdirAll(projectConfigPath, os.ModePerm); err != nil {
    return fmt.Errorf("error creating project config directory: %v", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if err := unix.Access(projectConfigPath, unix.W_OK); err != nil {
    return fmt.Errorf("config path not writable: %v", err)
}
if _, err := os.Stat(projectConfigPath); os.IsNotExist(err) {
    os.MkdirAll(projectConfigPath, 0o755)
}

Try / catch

if err := askai.SaveProjectConfig(domain, cfg); err != nil {
    if strings.Contains(err.Error(), "writing") {
        // check disk/permissions before surfacing
        return fmt.Errorf("persisting project config failed (check disk space & permissions): %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: os.WriteFile fails because the parent directory does not exist (MkdirAll skipped/failed), permission denied, disk full, or read-only filesystem.

Common situations: Docker volume mounted read-only; disk quota/full disk on the config volume; MkdirAll failed earlier but its error was ignored; running container as non-root user without write access to PRODUCT_CONFIG_PATH.

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/e1ac583a7e9b139e. Report an issue: GitHub.