Billionmail/BillionMail · error

error marshalling project configuration: %v

Error message

error marshalling project configuration: %v

What it means

SaveProjectConfig serializes the ProjectConfig to indented JSON with json.MarshalIndent before writing to disk; if marshalling fails (unsupported field types like channels, funcs, or invalid maps), it returns this wrapped error and nothing is written.

Source

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

		return ProjectConfig{}, fmt.Errorf("error unmarshalling project configuration: %v", err)
	}
	return config, nil
}

// 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")

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Review recently added ProjectConfig fields; tag unsupported ones with `json:"-"` or change their types
  2. Implement MarshalJSON for any custom field type that fails to encode
  3. Use the wrapped %v cause to identify which value json cannot handle
  4. Add a unit test marshalling a fully-populated ProjectConfig to catch this at build time

Example fix

// before
type ProjectConfig struct {
    Callback func() `json:"callback"` // unsupported
}
// after
type ProjectConfig struct {
    Callback func() `json:"-"` // excluded from JSON
}
Defensive patterns

Strategy: validation

Validate before calling

if _, err := json.MarshalIndent(config, "", "  "); err != nil {
    return fmt.Errorf("config not JSON-serializable: %v", err)
} // run before calling SaveProjectConfig

Try / catch

if err := askai.SaveProjectConfig(domain, cfg); err != nil {
    if strings.Contains(err.Error(), "marshalling") {
        log.Errorf("ProjectConfig has non-serializable fields: %v", err)
        return err
    }
    return err
}

Prevention

When it happens

Trigger: SaveProjectConfig (via Create, SetProjectStatus, ModifyBaseInfo, AppendProjectConfig) receives a ProjectConfig containing values json cannot encode — e.g. a field added with an unsupported type (chan, func, complex), a map with non-string keys, or a circular structure.

Common situations: A developer extends ProjectConfig with a new field of a non-JSON-serializable type; embedding a struct containing sync primitives; custom types without MarshalJSON handling malformed data.

Understand the failure class

Background: json.Marshal / "failed to marshal" errors in Go: why "unsupported type" happens and how to fix it — this error's family across 22 libraries.

Related errors


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