{"record":{"id":"8db07441d9c4f09a","repo":"Billionmail/BillionMail","slug":"error-reading-project-configuration-file-v","errorCode":null,"errorMessage":"error reading project configuration file: %v","messagePattern":"error reading project configuration file: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":110,"sourceCode":"\tAltText    string `json:\"alt_text\"`    // 图片替代文本\n\tImageTag   string `json:\"image_tag\"`   // 图片标签\n\tUpdateTime int64  `json:\"update_time\"` // 更新时间\n\tSize       string `json:\"size\"`        // 图片大小 80x80\n}\n\n// 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)","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L92-L128","documentation":"After confirming project.json exists, ReadProjectConfig reads it with os.ReadFile; any OS-level read failure (permissions, race deletion, I/O error) is wrapped as this error including the underlying cause.","triggerScenarios":"os.ReadFile fails on PRODUCT_CONFIG_PATH/<Domain>/project.json — file exists at check time but is deleted before read (TOCTOU), permission denied, path is a directory, or disk I/O error.","commonSituations":"File permissions changed by another process/container user mismatch; concurrent SaveProjectConfig write with replace semantics breaking readers; the 'file' is actually a directory created by mistake; read-only or failing disk.","solutions":["Check file permissions/ownership for the process user on the project.json path","Inspect the wrapped %v cause in the error to distinguish permission vs I/O vs path issues","Verify the path is a regular file, not a directory","Retry the read; if it was a race with a concurrent save, a second read typically succeeds"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"info, err := os.Stat(filename)\nif err != nil || !info.Mode().IsRegular() {\n    return fmt.Errorf(\"%s is not a readable regular file\", filename)\n}\nf, err := os.Open(filename)\nif err != nil { return err }\nf.Close()","typeGuard":null,"tryCatchPattern":"var cfg askai.ProjectConfig\nfor i := 0; i < 3; i++ {\n    cfg, err = askai.ReadProjectConfig(domain)\n    if err == nil || !strings.Contains(err.Error(), \"error reading\") { break }\n    time.Sleep(100 * time.Millisecond) // transient read race\n}\nif err != nil { return err }","preventionTips":["Check file permissions and process user on the config directory","Verify PRODUCT_CONFIG_PATH points at a regular file tree, not a symlinked directory loop","Monitor disk health on the volume hosting configs","Avoid concurrent readers/writers without atomic rename in SaveProjectConfig"],"tags":["go","filesystem","io","permissions"],"backgroundTag":"file-read-failed","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}