Billionmail/BillionMail · error

error saving project configuration: %v

Error message

error saving project configuration: %v

What it means

After reading and modifying the config, SetProjectStatus persists it with SaveProjectConfig. If the save fails (write error, serialization error, directory missing/permissions), the error is wrapped as 'error saving project configuration: %v'. The in-memory status change is lost and the on-disk config is unchanged or possibly partially written.

Source

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

	// Return the project status
	return config.Status, true, nil
}

// SetProjectStatus updates the status of a project configuration based on the provided domain.
// It reads the existing configuration, modifies the status, and saves the updated configuration back to the file.
func SetProjectStatus(Domain string, status bool) error {
	config, err := ReadProjectConfig(Domain)
	if err != nil {
		return fmt.Errorf("error reading project configuration: %v", err)
	}

	// Update the project status
	config.Status = status

	err = SaveProjectConfig(Domain, config)
	if err != nil {
		return fmt.Errorf("error saving project configuration: %v", err)
	}
	return nil
}

// ModifyBaseInfo updates the base information of a project configuration based on the provided domain.
// It reads the existing 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 ModifyBaseInfo(Domain string, projectName, description, industry, primaryLogo, secondaryLogo, favicon string, urls []string) error {
	config, err := ReadProjectConfig(Domain)
	if err != nil {
		return fmt.Errorf("error reading project configuration: %v", err)
	}

	// Update the base information
	if projectName != "" {
		config.ProjectName = projectName
	}
	if description != "" {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the process has write permission on PRODUCT_CONFIG_PATH/<domain>/ and the config file
  2. Confirm the disk has free space and the mount is not read-only
  3. Ensure the config directory exists (SaveProjectConfig should create it; verify it did)
  4. Add file locking or serialize writes to avoid concurrent-save corruption
  5. Inspect the wrapped inner error to pinpoint write vs marshal failure

Example fix

// before
err := askai.SetProjectStatus(domain, true)
// after
if err := askai.SetProjectStatus(domain, true); err != nil {
    if strings.Contains(err.Error(), "saving project configuration") {
        os.MkdirAll(fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s", domain), 0o755)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

dir := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s", domain)
if fi, err := os.Stat(dir); err != nil || !fi.IsDir() {
    os.MkdirAll(dir, 0o755)
}
if err := unix.Access(dir, unix.W_OK); err != nil {
    return fmt.Errorf("config dir not writable: %w", err)
}

Type guard

func configDirWritable(domain string) bool {
    probe := fmt.Sprintf(PRODUCT_CONFIG_PATH+"/%s/.probe", domain)
    if err := os.WriteFile(probe, []byte("1"), 0o644); err != nil { return false }
    os.Remove(probe)
    return true
}

Try / catch

err := askai.SetProjectStatus(domain, false)
if err != nil && strings.Contains(err.Error(), "error saving project configuration") {
    // check disk space/permissions, then retry
    return retryAfterFix(func() error { return askai.SetProjectStatus(domain, false) })
}

Prevention

When it happens

Trigger: SetProjectStatus(Domain, status) where SaveProjectConfig fails writing PRODUCT_CONFIG_PATH/<domain>/ config: permission denied, directory absent, disk full, or marshal error.

Common situations: Read-only volume in container deployments; config directory deleted between read and save; disk quota exceeded; concurrent writers racing on the same file causing partial writes.

Related errors


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