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
Decode reads the HTTP request body into the contract's field map via json.Decoder.Decode. When the body is completely empty or ends prematurely, Decode returns io.EOF, which this code replaces with an explicit message before wrapping it with the failing contract path. It exists so callers can distinguish 'no/short body' from malformed JSON.
Solutions
- Ensure the client sends a valid JSON body with the required fields for this endpoint
- Check Content-Length / Transfer-Encoding and that the client actually flushes the payload
- If the endpoint legitimately accepts no body, mark its contract field with LegacyOptionalBody
- Inspect intermediaries (proxies, load balancers) that may truncate request bodies
Example fix
// before: client sends request without body
req, _ := http.NewRequest("POST", url, nil)
// after
body := []byte(`{"id":"20240101120000-abc"}`)
req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json") Defensive patterns
Strategy: validation
Validate before calling
function ensureBody(body) {
if (!body || (typeof body === "string" && body.trim() === "")) throw new Error("Request body is empty; supply a JSON payload")
const text = typeof body === "string" ? body : JSON.stringify(body)
try { JSON.parse(text) } catch (e) { throw new Error("Request body is not complete JSON: " + e.message) }
return text
} Type guard
function hasBody(req) { return req.body !== undefined && req.body !== null && Object.keys(req.body).length > 0 } Try / catch
try { await api.call(payload) } catch (e) { if (/empty or truncated \(EOF\)/.test(e.message)) { console.error("Body was empty/truncated — check client serialization and proxies", payload) } else { throw e } } Prevention
- Always set Content-Type: application/json and send a serialized body
- Verify Content-Length matches bytes actually sent when crafting raw requests
- For no-body endpoints, confirm the contract declares LegacyOptionalBody
- Test through the same proxy chain used in production
When it happens
Trigger: Sending a POST/PUT request to an apicontract endpoint with a zero-length body, a Content-Length larger than the actual bytes sent, or a connection cut mid-body so the JSON is truncated.
Common situations: Forgetting to pass a body for endpoints whose contract does not declare LegacyOptionalBody; proxy/gateway buffering stripping the body; streaming clients that close the connection before flushing; tests calling the handler directly with no io.Reader content.
Related errors
- the request body is empty or truncated (EOF)
- createEmptyParagraph must be a boolean
- failed to read body
- Field [ ] has an invalid type
- Field [ ] is required
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/306f9070bf96dfa0.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/decode.go:38
}
if e.decodeRequest != nil {
return e.decodeRequest(reader)
}
if e.definition.Body == StructJSONBody {
err = json.NewDecoder(reader).Decode(&request)
if err != nil {
err = fmt.Errorf("Parses request [%s] failed: %s", e.definition.Path, err)
}
return
}
var fields map[string]json.RawMessage
err = json.NewDecoder(reader).Decode(&fields)
if err != nil {
if e.definition.Body == LegacyOptionalBody {
return request, nil
}
if errors.Is(err, io.EOF) {
err = errors.New("the request body is empty or truncated (EOF)")
}
return request, fmt.Errorf("Parses request [%s] failed: %s", e.definition.Path, err)
}
value := reflect.ValueOf(&request).Elem()
if value.Kind() != reflect.Struct {
return request, fmt.Errorf("request contract must be a struct")
}
err = decodeRequestFields(value, fields)
return request, err
}
func decodeRequestFields(value reflect.Value, fields map[string]json.RawMessage) error {
for i := 0; i < value.NumField(); i++ {
field := value.Type().Field(i)
name := strings.Split(field.Tag.Get("json"), ",")[0]
if (!field.IsExported() && !field.Anonymous) || name == "-" {
continue
}View on GitHub (pinned to 9f775e8a12)