siyuan-note/siyuan · error
Invalid rich clipboard asset at index
Error message
Invalid rich clipboard asset at index [%d]
What it means
Each rich clipboard asset must carry a non-negative integer index and a non-empty string path; an optional box must be usable as context. This aggregate check fires when index is null/non-numeric/negative/fractional, or path is missing, non-string, or whitespace-only.
Solutions
- Ensure index is a non-negative integer (Math.floor / Math.trunc it before sending)
- Ensure path is a non-empty, non-whitespace string (trim it; it is trimmed server-side anyway)
- Include every required key (index, path) in each asset object; box is the only optional key
Example fix
// before
{index: 1.5, path: ""}
// after
{index: 1, path: "/assets/foo.png"} Defensive patterns
Strategy: validation
Validate before calling
for (const [i, a] of assets.entries()) {
if (!Number.isInteger(a.index) || a.index < 0) throw new Error(`assets[${i}].index must be a non-negative integer`);
if (typeof a.path !== 'string' || !a.path.trim()) throw new Error(`assets[${i}].path must be a non-empty string`);
} Type guard
const isValidAsset = (a) => a && Number.isInteger(a.index) && a.index >= 0 && typeof a.path === 'string' && a.path.trim() !== '';
Prevention
- Math.floor computed indices before sending
- Trim paths client-side; never send whitespace-only paths
- Always include index and path; box is the only optional key
When it happens
Trigger: Sending an asset like {"index":-1,"path":"/a.png"} (negative index), {"index":1.5,"path":"/a.png"} (non-integer), {"index":0} (missing path), or {"index":0,"path":" "} (blank path) to /api/clipboard/prepareRichText.
Common situations: Client computes index from a zero-based vs one-based list inconsistently; asset path built from an empty variable; floats leaking from JavaScript arithmetic producing 0.30000000000000004-style indices.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Field [assets] must not be empty
- Field [batch] must not be empty
- Field [groups] must not be empty
- Field [assets. .box] should be of type [String]
- Field [assets. ] should be of type [Object]
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/f92655f0a15011d6.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/apicontract/clipboard_input.go:33
return request, err
}
assets, err := legacyField[[]json.RawMessage](fields, "assets", "Array", true)
if err != nil {
return request, err
}
if len(assets) == 0 {
return request, fmt.Errorf("Field [assets] must not be empty")
}
request.Assets = make([]RichClipboardAsset, 0, len(assets))
for i, raw := range assets {
var asset map[string]json.RawMessage
if json.Unmarshal(raw, &asset) != nil || asset == nil {
return request, fmt.Errorf("Field [assets.%d] should be of type [Object]", i)
}
var index float64
var path, box string
if bytes.Equal(asset["index"], []byte("null")) || json.Unmarshal(asset["index"], &index) != nil || index < 0 || index != float64(int(index)) || json.Unmarshal(asset["path"], &path) != nil || strings.TrimSpace(path) == "" {
return request, fmt.Errorf("Invalid rich clipboard asset at index [%d]", i)
}
if value, exists := asset["box"]; exists {
if bytes.Equal(value, []byte("null")) || json.Unmarshal(value, &box) != nil {
return request, fmt.Errorf("Field [assets.%d.box] should be of type [String]", i)
}
}
request.Assets = append(request.Assets, RichClipboardAsset{Index: int(index), Path: strings.TrimSpace(path), Box: strings.TrimSpace(box)})
}
return
}
CleanupRichText.decodeRequest = func(reader io.Reader) (request CleanupRichTextRequest, err error) {
fields, err := blockRequestFields(reader, "/api/clipboard/cleanupRichText")
if err != nil {
return request, err
}
request.Batch, err = legacyField[string](fields, "batch", "String", true)
if err != nil {
return request, errView on GitHub (pinned to 9f775e8a12)