{"record":{"id":"e9bf609d265f9848","repo":"Billionmail/BillionMail","slug":"error-unmarshalling-project-configuration-v","errorCode":null,"errorMessage":"error unmarshalling project configuration: %v","messagePattern":"error unmarshalling project configuration: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":116,"sourceCode":"// ReadProjectConfig reads the project configuration from a JSON file based on the provided domain.\n// It returns a ProjectConfig struct or an error if the file does not exist or cannot be read.\nfunc ReadProjectConfig(Domain string) (ProjectConfig, error) {\n\n\tfilename := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s/project.json\", Domain)\n\t// Here you would implement the logic to read the project configuration from the file.\n\t// For now, we will just return a placeholder string.\n\tif !public.FileExists(filename) {\n\t\treturn ProjectConfig{}, fmt.Errorf(\"project configuration file does not exist: %s\", filename)\n\t}\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn ProjectConfig{}, fmt.Errorf(\"error reading project configuration file: %v\", err)\n\t}\n\n\tvar config ProjectConfig\n\terr = json.Unmarshal(data, &config)\n\tif err != nil {\n\t\treturn ProjectConfig{}, fmt.Errorf(\"error unmarshalling project configuration: %v\", err)\n\t}\n\treturn config, nil\n}\n\n// SaveProjectConfig saves the provided ProjectConfig to a JSON file based on the domain.\n// It creates the directory if it does not exist and writes the configuration to project.json.\n// If the file cannot be written, it returns an error.\n// If the directory does not exist, it creates it with appropriate permissions.\nfunc SaveProjectConfig(Domain string, config ProjectConfig) error {\n\tprojectConfigPath := fmt.Sprintf(\"%s/%s\", PRODUCT_CONFIG_PATH, Domain)\n\tif !public.FileExists(projectConfigPath) {\n\t\tos.MkdirAll(projectConfigPath, os.ModePerm)\n\t}\n\tfilename := fmt.Sprintf(\"%s/project.json\", projectConfigPath)\n\n\tconfigStr, err := json.MarshalIndent(config, \"\", \"  \")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error marshalling project configuration: %v\", err)","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L98-L134","documentation":"The file bytes read from project.json are decoded into ProjectConfig with json.Unmarshal; if the JSON does not match the struct (corrupt file, invalid JSON, renamed fields), the failure is wrapped as this error.","triggerScenarios":"project.json contains invalid JSON (truncated write, manual edit), or fields whose types conflict with ProjectConfig (e.g. KnowledgeBase as object instead of array, Status as string instead of bool).","commonSituations":"A previous crash/power loss during os.WriteFile left a truncated file; hand-edited config with syntax errors; ProjectConfig struct fields renamed after a version upgrade while old files persist on disk.","solutions":["Validate/pretty-print project.json with a JSON linter to find the syntax problem","Restore the file by re-running Create/SaveProjectConfig or from backup","Compare file fields against the current ProjectConfig struct after upgrades; migrate old schemas","Harden SaveProjectConfig to write to a temp file and atomically rename to avoid truncation"],"exampleFix":"// before\nif err := json.Unmarshal(data, &config); err != nil {\n    return ProjectConfig{}, fmt.Errorf(\"error unmarshalling project configuration: %v\", err)\n}\n// after\nif err := json.Unmarshal(data, &config); err != nil {\n    // fall back to defaults so callers can re-init\n    return ProjectConfig{}, fmt.Errorf(\"error unmarshalling project configuration: %v\", err)\n}\n// caller:\nif strings.Contains(err.Error(), \"unmarshalling\") { os.Remove(filename); askai.Create(domain, urls) }","handlingStrategy":"validation","validationCode":"data, _ := os.ReadFile(filename)\nif !json.Valid(data) {\n    return fmt.Errorf(\"%s contains invalid JSON; re-create the project config\", filename)\n}","typeGuard":"func isValidProjectConfig(data []byte) bool {\n    var probe askai.ProjectConfig\n    return json.Unmarshal(data, &probe) == nil\n}","tryCatchPattern":"cfg, err := askai.ReadProjectConfig(domain)\nif err != nil && strings.Contains(err.Error(), \"unmarshalling\") {\n    os.Remove(filename) // quarantine corrupt file\n    return reinitializeProject(domain)\n}","preventionTips":["Make SaveProjectConfig write to a temp file then os.Rename for atomicity","Never hand-edit project.json without validating with a JSON parser afterwards","Run schema migrations when ProjectConfig struct fields change across versions","Back up the config directory before upgrades"],"tags":["go","json","config","deserialization"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}