{"record":{"id":"af462ed9c68c2e85","repo":"router-for-me/CLIProxyAPI","slug":"json-is-required","errorCode":null,"errorMessage":"json is required","messagePattern":"json is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/pluginhost/auth_callbacks.go","lineNumber":272,"sourceCode":"\t}\n\tvar metadata map[string]any\n\tif errUnmarshal := json.Unmarshal(data, &metadata); errUnmarshal != nil {\n\t\treturn nil, nil, fmt.Errorf(\"invalid auth file for auth_index %s: %w\", authIndex, errUnmarshal)\n\t}\n\treturn auth, data, nil\n}\n\nfunc validateHostAuthSaveRequest(req pluginapi.HostAuthSaveRequest) (string, []byte, error) {\n\tname := strings.TrimSpace(req.Name)\n\tif isUnsafeAuthFileName(name) {\n\t\treturn \"\", nil, fmt.Errorf(\"invalid auth file name\")\n\t}\n\tif !strings.HasSuffix(strings.ToLower(name), \".json\") {\n\t\treturn \"\", nil, fmt.Errorf(\"auth file name must end with .json\")\n\t}\n\trawJSON := bytesTrimSpace(req.JSON)\n\tif len(rawJSON) == 0 {\n\t\treturn \"\", nil, fmt.Errorf(\"json is required\")\n\t}\n\tvar metadata map[string]any\n\tif errUnmarshal := json.Unmarshal(rawJSON, &metadata); errUnmarshal != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"invalid auth json: %w\", errUnmarshal)\n\t}\n\treturn filepath.Base(name), rawJSON, nil\n}\n\nfunc (h *Host) saveAuthFile(ctx context.Context, name string, data []byte) (string, error) {\n\tauthDir := h.resolvedAuthDir()\n\tif authDir == \"\" {\n\t\treturn \"\", fmt.Errorf(\"auth directory is unavailable\")\n\t}\n\tdst := filepath.Join(authDir, filepath.Base(name))\n\tif !filepath.IsAbs(dst) {\n\t\tif abs, errAbs := filepath.Abs(dst); errAbs == nil {\n\t\t\tdst = abs\n\t\t}","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginhost/auth_callbacks.go#L254-L290","documentation":"Thrown by validateHostAuthSaveRequest when the request's JSON payload trims to zero length. The save callback requires a body; an empty (or whitespace-only) JSON field is rejected before any filesystem action.","triggerScenarios":"Plugin sends HostAuthSaveRequest{Name: \"x.json\", JSON: \"\"} or JSON containing only whitespace/newlines.","commonSituations":"Plugin bug where the marshaled struct is forgotten (zero value sent); serialization step silently produced empty output; a test stub omitting the field.","solutions":["Marshal the auth object and assign it to req.JSON before calling save","Add a pre-check in the plugin: len(bytes.TrimSpace(json)) > 0","Log the request (minus secrets) when save fails to catch empty payloads early"],"exampleFix":"// before\nreq := pluginapi.HostAuthSaveRequest{Name: name, JSON: string(data)} // data empty\n\n// after\nif len(bytes.TrimSpace(data)) == 0 { return errors.New(\"nothing to save\") }\nreq := pluginapi.HostAuthSaveRequest{Name: name, JSON: string(data)}","handlingStrategy":"validation","validationCode":"if len(bytes.TrimSpace(req.JSON)) == 0 {\n    return errors.New(\"refusing to save empty auth json\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always marshal the payload and assert non-empty before save","Add unit tests asserting the save path rejects zero-value requests"],"tags":["go","pluginhost","auth","validation","empty-payload"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}