siyuan-note/siyuan · error

failed to read body

Error message

failed to read body: %s

What it means

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.

Solutions

  1. Retry the request with an intact body over a stable connection
  2. Check intermediary proxies/logs for aborted transfers or body size limits
  3. Ensure the client sends a valid, complete request body with correct Content-Length/chunked framing
  4. Increase proxy/client body size limits if the session payload is large
Defensive patterns

Strategy: retry

Validate before calling

if (!(body instanceof Uint8Array) || body.length === 0) throw new Error("empty request body");

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/b12feb1139b5a12b. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/ai_input.go:70

	if raw := fields["providerConfig"]; len(raw) > 0 && !bytes.Equal(raw, []byte("null")) {
		value, err := legacyJSONValue[SettingProvider](raw)
		if err != nil {
			err = fmt.Errorf("%s", strings.NewReplacer("SettingProvider.", "Provider.", "SettingModel.", "Model.", "apicontract.SettingProvider", "conf.Provider", "apicontract.SettingModel", "conf.Model").Replace(err.Error()))
		}
		request.ProviderConfig = &value
		request.providerError = err
	}
	return request
}

func init() {
	aiStructDecoder(&AIEditorChat, "aiEditorChatReq")
	aiStructDecoder(&AIAgentChat, "agentChatReq")
	aiStructDecoder(&AIGetSession, "agentSessionGetReq")
	AISaveSession.decodeRequest = func(reader io.Reader) (value AISession, err error) {
		value.raw, err = io.ReadAll(reader)
		if err != nil {
			err = fmt.Errorf("failed to read body: %s", err)
		}
		return
	}
	aiStructDecoder(&AIAgentConfirm, "agentConfirmReq")
	aiStructDecoder(&AIAgentSetPermission, "agentPermissionReq")
	aiStructDecoder(&AIAgentQuestion, "agentQuestionReq")
	aiStructDecoder(&AIAgentBrowserCapabilityResult, "agentBrowserCapabilityResultReq")
	aiStructDecoder(&AIAgentTitle, "agentTitleReq")
	aiStructDecoder(&AIListSessions, "agentSessionsReq")
	aiStructDecoder(&AIRemoveSession, "agentSessionDeleteReq")
	aiStructDecoder(&AIGetSkill, "skillGetReq")
	aiStructDecoder(&AISaveSkill, "skillSaveReq")
	aiStructDecoder(&AIRemoveSkill, "skillRemoveReq")
	aiStructDecoder(&AIRenameSkill, "skillRenameReq")
	aiJSONDecoder(&AIChatGPT, func(fields fileTreeFields) (value AIMessageRequest, err error) {
		value.Msg, err = aiLegacyString(fields, "msg", true, true)
		return
	})

View on GitHub (pinned to 9f775e8a12)