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

decodeSettingConfig decodes the request body of setting-related config endpoints into a JSON object. When json.Decoder returns io.EOF it means the body was empty or cut off mid-stream, and the decoder replaces the bare EOF with this explicit message, then wraps it as 'Parses request [path] failed'.

Solutions

  1. Attach a valid JSON object body to the request
  2. Set Content-Type: application/json and re-send
  3. Check network path (proxy, body size limits) for truncation
  4. Catch the wrapped error and log request body length to confirm emptiness

Example fix

// before
await fetch('/api/setting/setConfig', { method: 'POST' })
// after
await fetch('/api/setting/setConfig', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(config) })
Defensive patterns

Strategy: validation

Validate before calling

async function postJson(url: string, payload: unknown) {
  const body = JSON.stringify(payload);
  if (!body || body === '{}') throw new Error('refusing to send empty body');
  return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body });
}

Try / catch

try { const res = await api.call(cmd, payload); } catch (e) { if (String(e).includes('empty or truncated (EOF)')) { retryWithBody(cmd, payload); } }

Prevention

When it happens

Trigger: POSTing to a setting config endpoint (routed through decodeSettingConfig) with no body at all, a zero-length body, or a body truncated before a complete JSON value arrives.

Common situations: Fetch/axios calls that omit `body` on POST, proxies or load balancers cutting off large request bodies, clients aborting mid-upload, or tests forgetting to attach a payload.

Related errors


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

Appendix: source

Thrown at kernel/apicontract/setting_config.go:31

type settingConfig struct {
	raw         json.RawMessage
	fields      map[string]json.RawMessage
	configError error
}

func (r settingConfig) ConfigJSON() []byte        { return append([]byte(nil), r.raw...) }
func (r settingConfig) ConfigError() error        { return r.configError }
func (r settingConfig) HasField(name string) bool { _, ok := r.fields[name]; return ok }

func decodeSettingConfig(reader io.Reader, path string, removeApp bool) (r settingConfig, err error) {
	var raw json.RawMessage
	err = json.NewDecoder(reader).Decode(&raw)
	if err == nil {
		r.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
	}
	if removeApp {
		delete(r.fields, "app")
	}
	r.raw, err = json.Marshal(r.fields)
	return
}

func settingConfigError(err error) error {
	if err == nil {
		return nil
	}
	message := strings.ReplaceAll(err.Error(), "apicontract.Setting", "conf.")
	message = strings.ReplaceAll(message, "field Setting", "field ")

View on GitHub (pinned to 9f775e8a12)