{"record":{"id":"e5b3fb333562266d","repo":"Billionmail/BillionMail","slug":"error-unmarshalling-prompt-config-v","errorCode":null,"errorMessage":"error unmarshalling prompt config: %v","messagePattern":"error unmarshalling prompt config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":807,"sourceCode":"\n\t\tif err != nil {\n\t\t\treturn PromptConfig{}, fmt.Errorf(\"error marshalling default prompt config: %v\", err)\n\t\t}\n\t\terr = os.WriteFile(filename, defaultPromptJson, 0644)\n\t\tif err != nil {\n\t\t\treturn PromptConfig{}, fmt.Errorf(\"error saving default prompt config file: %v\", err)\n\t\t}\n\t\treturn defaultPromptConfig, nil\n\t}\n\tdata, err := os.ReadFile(filename)\n\tif err != nil {\n\t\treturn PromptConfig{}, fmt.Errorf(\"error reading prompt config file: %v\", err)\n\t}\n\n\tvar config PromptConfig\n\terr = json.Unmarshal(data, &config)\n\tif err != nil {\n\t\treturn PromptConfig{}, fmt.Errorf(\"error unmarshalling prompt config: %v\", err)\n\t}\n\treturn config, nil\n}\n\n// ModifyPrompt updates the prompt configuration for a given domain.\n// It reads the existing prompt configuration, modifies the specified fields, and saves the updated configuration back to the file.\n// If the prompt is empty, it will not be updated.\nfunc ModifyPrompt(Domain string, Prompt string) error {\n\tconfig, err := GetPrompt(Domain)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting prompt config: %v\", err)\n\t}\n\n\t// Update the prompt configuration\n\tif Prompt != \"\" {\n\t\tconfig.Prompt = Prompt\n\t}\n","sourceCodeStart":789,"sourceCodeEnd":825,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L789-L825","documentation":"GetPrompt reads PRODUCT_CONFIG_PATH/<Domain>/prompt_config.json into a PromptConfig struct. This error is wrapped when json.Unmarshal fails, meaning the file exists but its bytes are not valid JSON or do not match the PromptConfig shape (e.g. a JSON string where an object is expected). It is thrown so callers like ModifyPrompt fail fast instead of silently operating on a zero-value config.","triggerScenarios":"Calling GetPrompt(domain) when prompt_config.json contains malformed JSON — truncated writes, manual edits with syntax errors, BOM bytes, or the file holding a JSON array/string instead of an object with a 'prompt' field.","commonSituations":"Someone hand-edited prompt_config.json and broke the syntax; a concurrent write was interrupted leaving a partial file; an upgrade changed the config schema; the file was overwritten with HTML/error output by another tool.","solutions":["Open PRODUCT_CONFIG_PATH/<Domain>/prompt_config.json and validate it with `jq .` or `python -m json.tool` to find the syntax error","Fix the JSON manually or delete the file — GetPrompt recreates it from the default config on next call","Ensure writes are atomic (write to temp file then os.Rename) to prevent truncated/partial configs","Check the file has no UTF-8 BOM and is not empty"],"exampleFix":"// before\n$ cat prompt_config.json\n{\"prompt\": \"Hello\"  // trailing comment breaks JSON\n\n// after\n$ cat prompt_config.json\n{\"prompt\": \"Hello\", \"update_time\": 1725500000}","handlingStrategy":"validation","validationCode":"// validate the config file before calling GetPrompt\nfunc validatePromptConfig(domain string) error {\n\tdata, err := os.ReadFile(fmt.Sprintf(\"%s/%s/prompt_config.json\", PRODUCT_CONFIG_PATH, domain))\n\tif err != nil {\n\t\treturn err // file missing: GetPrompt will create a default\n\t}\n\tif len(bytes.TrimPrefix(data, []byte{0xEF, 0xBB, 0xBF})) == 0 {\n\t\treturn errors.New(\"config file is empty\")\n\t}\n\tvar cfg map[string]any\n\treturn json.Unmarshal(data, &cfg) // syntax/shape check\n}","typeGuard":"func isValidPromptConfigJSON(data []byte) bool {\n\tvar cfg map[string]any\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\treturn false\n\t}\n\t_, ok := cfg[\"prompt\"]\n\treturn ok\n}","tryCatchPattern":"cfg, err := GetPrompt(domain)\nif err != nil {\n\tif strings.Contains(err.Error(), \"unmarshalling\") {\n\t\t// corrupt file: reset to default\n\t\tos.Remove(filepath.Join(PRODUCT_CONFIG_PATH, domain, \"prompt_config.json\"))\n\t\tcfg, err = GetPrompt(domain)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n}","preventionTips":["Validate prompt_config.json with jq after any manual edit","Write config atomically (temp file + os.Rename) to avoid truncated files","Add a startup check that every domain config parses, logging corrupt ones","Never hand-edit configs on production without a JSON syntax check"],"tags":["json","unmarshal","config","go"],"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"}