{"record":{"id":"eacf598046640be0","repo":"Billionmail/BillionMail","slug":"error-saving-project-configuration-v","errorCode":null,"errorMessage":"error saving project configuration: %v","messagePattern":"error saving project configuration: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":239,"sourceCode":"\n\t// Return the project status\n\treturn config.Status, true, nil\n}\n\n// SetProjectStatus updates the status of a project configuration based on the provided domain.\n// It reads the existing configuration, modifies the status, and saves the updated configuration back to the file.\nfunc SetProjectStatus(Domain string, status bool) error {\n\tconfig, err := ReadProjectConfig(Domain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading project configuration: %v\", err)\n\t}\n\n\t// Update the project status\n\tconfig.Status = status\n\n\terr = SaveProjectConfig(Domain, config)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error saving project configuration: %v\", err)\n\t}\n\treturn nil\n}\n\n// ModifyBaseInfo updates the base information of a project configuration based on the provided domain.\n// It reads the existing configuration, modifies the specified fields, and saves the updated configuration back to the file.\n// If any field is empty, it will not be updated.\nfunc ModifyBaseInfo(Domain string, projectName, description, industry, primaryLogo, secondaryLogo, favicon string, urls []string) error {\n\tconfig, err := ReadProjectConfig(Domain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading project configuration: %v\", err)\n\t}\n\n\t// Update the base information\n\tif projectName != \"\" {\n\t\tconfig.ProjectName = projectName\n\t}\n\tif description != \"\" {","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L221-L257","documentation":"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.","triggerScenarios":"SetProjectStatus(Domain, status) where SaveProjectConfig fails writing PRODUCT_CONFIG_PATH/<domain>/ config: permission denied, directory absent, disk full, or marshal error.","commonSituations":"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.","solutions":["Check the process has write permission on PRODUCT_CONFIG_PATH/<domain>/ and the config file","Confirm the disk has free space and the mount is not read-only","Ensure the config directory exists (SaveProjectConfig should create it; verify it did)","Add file locking or serialize writes to avoid concurrent-save corruption","Inspect the wrapped inner error to pinpoint write vs marshal failure"],"exampleFix":"// before\nerr := askai.SetProjectStatus(domain, true)\n// after\nif err := askai.SetProjectStatus(domain, true); err != nil {\n    if strings.Contains(err.Error(), \"saving project configuration\") {\n        os.MkdirAll(fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s\", domain), 0o755)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"dir := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s\", domain)\nif fi, err := os.Stat(dir); err != nil || !fi.IsDir() {\n    os.MkdirAll(dir, 0o755)\n}\nif err := unix.Access(dir, unix.W_OK); err != nil {\n    return fmt.Errorf(\"config dir not writable: %w\", err)\n}","typeGuard":"func configDirWritable(domain string) bool {\n    probe := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s/.probe\", domain)\n    if err := os.WriteFile(probe, []byte(\"1\"), 0o644); err != nil { return false }\n    os.Remove(probe)\n    return true\n}","tryCatchPattern":"err := askai.SetProjectStatus(domain, false)\nif err != nil && strings.Contains(err.Error(), \"error saving project configuration\") {\n    // check disk space/permissions, then retry\n    return retryAfterFix(func() error { return askai.SetProjectStatus(domain, false) })\n}","preventionTips":["Check writable volumes and free disk before bulk updates","Ensure SaveProjectConfig creates its target directory","Serialize writes with a per-domain mutex","Use atomic write (temp file + rename) to avoid corruption","Alert on write failures via the wrapped inner error"],"tags":["go","file-io","config"],"backgroundTag":"config-file-write-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"}