siyuan-note/siyuan · error

the request body is empty or truncated (EOF)

Error message

the request body is empty or truncated (EOF)

What it means

syncRequestFields parses the whole body of sync-related endpoints (/api/sync/setSyncEnable, setSyncMode, setSyncProvider, setCloudSyncDir, performSync, etc.). If the JSON decoder hits io.EOF — meaning the request body is completely empty or was cut off mid-stream — it is replaced with this explicit message before being wrapped in 'Parses request [...] failed'.

Solutions

  1. Send a valid JSON object body, e.g. {"enabled":true} for /api/sync/setSyncEnable
  2. Add Content-Type: application/json and verify Content-Length matches the body
  3. Check client code that the body variable is actually passed and not undefined/null

Example fix

// before
curl -X POST http://127.0.0.1:6806/api/sync/setSyncEnable -H 'Authorization: Token xxx'
// after
curl -X POST http://127.0.0.1:6806/api/sync/setSyncEnable -H 'Authorization: Token xxx' -H 'Content-Type: application/json' -d '{"enabled":true}'
Defensive patterns

Strategy: validation

Validate before calling

if (!body || typeof body !== 'object') throw new Error('sync request body required');

Type guard

null

Try / catch

try { const res = await fetchPost(path, payload); } catch (e) { if (e.msg && e.msg.includes('empty or truncated')) fixRequestBody(); }

Prevention

When it happens

Trigger: POSTing to any of the /api/sync/* contract endpoints with no body, a Content-Length of 0, or a truncated/chunked stream; sending a body that is only whitespace; a proxy dropping the body.

Common situations: curl -X POST without -d; a fetch() call forgetting the body argument; network interruption between client and kernel truncating the body; misconfigured reverse proxy buffering.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/sync.go:151

func syncConfigError(err error, name string) error {
	if err == nil {
		return nil
	}
	detail := strings.ReplaceAll(err.Error(), "apicontract.Sync"+name, "conf."+name)
	return errors.New(strings.ReplaceAll(detail, "struct field Sync"+name+".", "struct field "+name+"."))
}

// syncRequestFields 保留整份请求的 JSON 数字解析、重复字段和首个对象读取语义。
func syncRequestFields(reader io.Reader, path string) (fields map[string]json.RawMessage, err error) {
	var raw json.RawMessage
	err = json.NewDecoder(reader).Decode(&raw)
	if err == nil {
		fields, err = legacyJSONValue[map[string]json.RawMessage](raw)
	}
	if err != nil {
		if errors.Is(err, io.EOF) {
			err = errors.New("the request body is empty or truncated (EOF)")
		}
		detail := strings.ReplaceAll(err.Error(), "map[string]json.RawMessage", "map[string]interface {}")
		err = fmt.Errorf("Parses request [%s] failed: %s", path, detail)
	}
	return
}

func init() {
	for _, endpoint := range []*Endpoint[SyncEnabledRequest, Null]{&SetSyncEnable, &SetSyncPerception, &SetSyncGenerateConflictDoc} {
		path := endpoint.definition.Path
		endpoint.decodeRequest = func(reader io.Reader) (r SyncEnabledRequest, err error) {
			fields, err := syncRequestFields(reader, path)
			if err == nil {
				r.Enabled, err = legacyField[bool](fields, "enabled", "Boolean", true)
			}
			return r, err
		}
	}

View on GitHub (pinned to 9f775e8a12)