{"record":{"id":"b12feb1139b5a12b","repo":"siyuan-note/siyuan","slug":"failed-to-read-body-s","errorCode":null,"errorMessage":"failed to read body: %s","messagePattern":"failed to read body: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/ai_input.go","lineNumber":70,"sourceCode":"\tif raw := fields[\"providerConfig\"]; len(raw) > 0 && !bytes.Equal(raw, []byte(\"null\")) {\n\t\tvalue, err := legacyJSONValue[SettingProvider](raw)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"%s\", strings.NewReplacer(\"SettingProvider.\", \"Provider.\", \"SettingModel.\", \"Model.\", \"apicontract.SettingProvider\", \"conf.Provider\", \"apicontract.SettingModel\", \"conf.Model\").Replace(err.Error()))\n\t\t}\n\t\trequest.ProviderConfig = &value\n\t\trequest.providerError = err\n\t}\n\treturn request\n}\n\nfunc init() {\n\taiStructDecoder(&AIEditorChat, \"aiEditorChatReq\")\n\taiStructDecoder(&AIAgentChat, \"agentChatReq\")\n\taiStructDecoder(&AIGetSession, \"agentSessionGetReq\")\n\tAISaveSession.decodeRequest = func(reader io.Reader) (value AISession, err error) {\n\t\tvalue.raw, err = io.ReadAll(reader)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to read body: %s\", err)\n\t\t}\n\t\treturn\n\t}\n\taiStructDecoder(&AIAgentConfirm, \"agentConfirmReq\")\n\taiStructDecoder(&AIAgentSetPermission, \"agentPermissionReq\")\n\taiStructDecoder(&AIAgentQuestion, \"agentQuestionReq\")\n\taiStructDecoder(&AIAgentBrowserCapabilityResult, \"agentBrowserCapabilityResultReq\")\n\taiStructDecoder(&AIAgentTitle, \"agentTitleReq\")\n\taiStructDecoder(&AIListSessions, \"agentSessionsReq\")\n\taiStructDecoder(&AIRemoveSession, \"agentSessionDeleteReq\")\n\taiStructDecoder(&AIGetSkill, \"skillGetReq\")\n\taiStructDecoder(&AISaveSkill, \"skillSaveReq\")\n\taiStructDecoder(&AIRemoveSkill, \"skillRemoveReq\")\n\taiStructDecoder(&AIRenameSkill, \"skillRenameReq\")\n\taiJSONDecoder(&AIChatGPT, func(fields fileTreeFields) (value AIMessageRequest, err error) {\n\t\tvalue.Msg, err = aiLegacyString(fields, \"msg\", true, true)\n\t\treturn\n\t})","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/ai_input.go#L52-L88","documentation":"The AISaveSession endpoint reads the entire raw request body with io.ReadAll; if that read fails the error is wrapped as 'failed to read body: <err>'. This indicates the request body could not be read at the transport/IO level before any JSON parsing happened.","triggerScenarios":"POST to the AI session save endpoint where the request body stream errors: aborted connections, request smuggling/mis-framed chunked bodies, proxies terminating mid-body, or passing a failing reader in in-process tests.","commonSituations":"Reverse proxies (nginx/CDN) dropping large uploads, client disconnecting before the body finishes, network interruptions on mobile, broken custom HTTP clients sending malformed chunked encoding.","solutions":["Retry the request with an intact body over a stable connection","Check intermediary proxies/logs for aborted transfers or body size limits","Ensure the client sends a valid, complete request body with correct Content-Length/chunked framing","Increase proxy/client body size limits if the session payload is large"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!(body instanceof Uint8Array) || body.length === 0) throw new Error(\"empty request body\");","typeGuard":null,"tryCatchPattern":"try { await post(\"/api/ai/session/save\", body); } catch (e) { if (e instanceof TypeError || /network|body/i.test(String(e))) { /* retry with backoff */ } else throw e; }","preventionTips":["Avoid proxies/body-size limits truncating large session payloads","Use a standard HTTP client that sets correct Content-Length or chunked encoding","Log and surface aborted-connection errors client-side"],"tags":["network","http","request-body"],"backgroundTag":"http-request-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}