{"record":{"id":"3fdf6c29a07de618","repo":"Billionmail/BillionMail","slug":"error-marshalling-default-prompt-config-v","errorCode":null,"errorMessage":"error marshalling default prompt config: %v","messagePattern":"error marshalling default prompt config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/askai/project.go","lineNumber":791,"sourceCode":"// GetPrompt retrieves the prompt configuration for a given domain from a JSON file.\n// It reads the prompt configuration file and returns a PromptConfig struct.\n// If the file does not exist or cannot be read, it returns an error.\nfunc GetPrompt(Domain string) (PromptConfig, error) {\n\tfilename := fmt.Sprintf(PRODUCT_CONFIG_PATH+\"/%s/prompt_config.json\", Domain)\n\tif !public.FileExists(filename) {\n\t\t// If the prompt config file does not exist, return a default prompt config\n\t\t// This allows the system to handle cases where the prompt configuration has not been set up\n\t\t// and avoids errors when trying to read a non-existent file.\n\t\t// It also allows the user to create a new prompt configuration without needing to handle file\n\t\t// not found errors.\n\t\tdefaultPromptConfig := PromptConfig{\n\t\t\tPrompt: \"This is a default prompt. Please customize it.\",\n\t\t}\n\t\tdefaultPromptConfig.UpdateTime = public.GetNowTime()\n\t\tdefaultPromptJson, err := json.MarshalIndent(defaultPromptConfig, \"\", \"  \")\n\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","sourceCodeStart":773,"sourceCodeEnd":809,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/project.go#L773-L809","documentation":"GetPrompt fails at project.go:791 when json.MarshalIndent cannot serialize the freshly constructed default PromptConfig before writing prompt_config.json for a domain whose config file does not exist. Since the default struct is hardcoded (Prompt string + UpdateTime), this error is essentially unreachable with the stock struct; it would only fire if PromptConfig gains an unserializable field or a custom marshaler that errors. The wrapped json error is returned to the ModifyPrompt caller.","triggerScenarios":"Calling GetPrompt(domain) (directly or via ModifyPrompt) when prompt_config.json does not exist AND the default PromptConfig value fails MarshalIndent — only possible with a modified PromptConfig containing func/chan fields, a cyclic value, or an erroring custom MarshalJSON.","commonSituations":"A developer extends PromptConfig with an unsupported type (e.g., a client handle or function field) and then first-run initialization on a new domain hits the default-config branch; a custom MarshalJSON added to PromptConfig returns an error.","solutions":["Check the wrapped MarshalIndent error for the offending field name/type on PromptConfig.","Remove or serialize (as string/ID) any unsupported fields added to PromptConfig.","Fix error paths in any custom MarshalJSON on PromptConfig or its field types.","Add a unit test that marshals the default PromptConfig so regressions surface immediately."],"exampleFix":"// before\ntype PromptConfig struct {\n    Prompt string\n    Hooks map[string]func(string) // not marshalable\n}\n// after\ntype PromptConfig struct {\n    Prompt string\n    HookNames []string `json:\"hookNames\"`\n}","handlingStrategy":"validation","validationCode":"func defaultPromptSerializable() error {\n    cfg := PromptConfig{Prompt: \"This is a default prompt. Please customize it.\"}\n    cfg.UpdateTime = public.GetNowTime()\n    _, err := json.MarshalIndent(cfg, \"\", \"  \")\n    return err\n}","typeGuard":"func isMarshalable(v any) bool {\n    _, err := json.Marshal(v)\n    return err == nil\n}","tryCatchPattern":"cfg, err := askai.GetPrompt(domain)\nif err != nil {\n    var mte *json.MarshalTypeError\n    if errors.As(err, &mte) {\n        log.Printf(\"PromptConfig field %s (%s) not serializable — fix struct\", mte.Field, mte.Type)\n    }\n    return err\n}","preventionTips":["Keep PromptConfig free of func/chan/complex field types.","Add a CI test that marshals the default PromptConfig so first-run init never regresses.","Prefer plain data types (string IDs over callbacks) in config structs.","Wrap MarshalIndent errors with the field name for faster diagnosis."],"tags":["go","json","serialization","config"],"backgroundTag":"json-marshal-unsupported-type","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"}