Billionmail/BillionMail · error

%s

Error message

%s

What it means

When any per-file rollback error was collected, rollback returns a single error whose message is the semicolon-joined list of all failure messages. The '%s' format is just an aggregator — the real causes are the embedded 'failed to read backup file ...' / 'failed to rollback file ...' strings inside it.

Source

Thrown at core/internal/service/multi_ip_domain/config_manager.go:124

	var allErrors []string
	for file, backup := range backups {
		if gfile.Exists(backup) {
			g.Log().Infof(ctx, "Rolling back %s from %s", file, backup)
			content, err := ioutil.ReadFile(backup)
			if err != nil {
				err = fmt.Errorf("failed to read backup file %s: %v", backup, err)
				allErrors = append(allErrors, err.Error())
				continue
			}

			if err := ioutil.WriteFile(file, content, 0644); err != nil {
				err = fmt.Errorf("failed to rollback file %s: %v", file, err)
				allErrors = append(allErrors, err.Error())
			}
		}
	}
	if len(allErrors) > 0 {
		return fmt.Errorf("%s", strings.Join(allErrors, "; "))
	}
	return nil
}

// cleanupBackups Clean up backup files
func (m *ConfigManager) cleanupBackups(ctx context.Context, backups map[string]string) {
	for _, backup := range backups {
		if gfile.Exists(backup) {
			g.Log().Debugf(ctx, "Cleaning up backup file: %s", backup)
			_ = os.Remove(backup)
		}
	}
}

// applyConfigs Apply configurations
func (m *ConfigManager) applyConfigs(ctx context.Context, configs []map[string]interface{}) error {
	// 1. Update docker-compose.yml
	if err := m.updateDockerCompose(ctx, configs); err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Split the message on '; ' to enumerate the individual per-file failures and address each (permissions, disk, missing backup).
  2. Manually restore each affected config from its .backup.<nano> file.
  3. Fix the underlying filesystem condition, then re-run ApplyConfigsWithRollback to reach a clean state.
  4. Prefer restructuring to return errors.Join/[]error instead of a joined string for programmatic handling.

Example fix

// before
if len(allErrors) > 0 {
	return fmt.Errorf("%s", strings.Join(allErrors, "; "))
}
// after
if len(allErrors) > 0 {
	return gerror.New("rollback failed for one or more files: " + strings.Join(allErrors, "; "))
}
Defensive patterns

Strategy: try-catch

Try / catch

if err := mgr.ApplyConfigsWithRollback(ctx, configs); err != nil {
	if strings.Contains(err.Error(), "rollback also failed") {
		for _, part := range strings.Split(err.Error(), "; ") {
			log.Printf("rollback sub-failure: %s", part)
		}
	}
}

Prevention

When it happens

Trigger: At least one backup read or restore-write failed during rollback after a failed apply; the joined messages surface as 'failed to apply configurations: <applyErr>, rollback also failed: <joined>'.

Common situations: Mixed failures: one backup file unreadable and another config file unwritable; caller sees this wrapper and must parse the joined string to find the root causes.

Related errors


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