siyuan-note/siyuan · error
invalid document ID [ ]
Error message
invalid document ID [%s]
What it means
UpdatePinnedDocs validates every document ID passed by the client before changing the pinned-documents list. ast.IsNodeIDPattern requires a canonical 22-character SiYuan node ID (base32-style pattern used for .sy file names). If any id in the ids slice is empty, truncated, or otherwise not a well-formed node ID, the whole update is rejected with this error before any storage mutation.
Solutions
- Fix the caller to pass real node IDs: fetch them from GetPinnedDocs / the doc tree rather than constructing strings.
- Validate each id against ast.IsNodeIDPattern client-side before sending the request.
- Check for empty strings or whitespace in the ids array and remove them.
- If IDs came from a manually edited pinned-docs storage file, restore IDs from the document tree or let maintainPinnedDocs clean them up.
Example fix
// before
UpdatePinnedDocs([]string{doc.Title}, "pin", "", false)
// after
UpdatePinnedDocs([]string{doc.ID}, "pin", "", false) Defensive patterns
Strategy: validation
Validate before calling
func canPin(ids []string) bool {
for _, id := range ids {
if !ast.IsNodeIDPattern(id) { return false }
}
return len(ids) > 0
} Type guard
func isValidDocID(id string) bool { return ast.IsNodeIDPattern(id) } Try / catch
if err := model.UpdatePinnedDocs(ids, "pin", "", false); err != nil {
if strings.HasPrefix(err.Error(), "invalid document ID") {
// refresh IDs from GetPinnedDocs and retry once
}
} Prevention
- Always take document IDs from API responses (GetPinnedDocs, doc tree), never construct them by hand
- Validate with ast.IsNodeIDPattern before each call
- Strip whitespace/empty entries from the ids array
- Keep client and kernel versions in sync to avoid ID format drift
When it happens
Trigger: Calling UpdatePinnedDocs (or the /api/filetree/updatePinnedDocs-style HTTP endpoint) with an ids array containing a malformed string: an empty id, a title pasted instead of an ID, a hand-constructed or truncated ID, or an ID copied from another system.
Common situations: Plugins or scripts building the request from stale data; clients that pass document titles or paths instead of block IDs; IDs corrupted by manual editing of the storage file or by an older client version that used a different ID format.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/5ff0eba53fe6922b.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/pinned_docs.go:175
// 根层顺序独立于源文档顺序,按相对位置更新以保留其他窗口新增的入口。
func UpdatePinnedDocs(ids []string, action, targetID string, after bool) error {
if len(ids) == 0 {
return fmt.Errorf("document IDs are required")
}
if action != "pin" && action != "unpin" {
return fmt.Errorf("invalid pinned document action")
}
pinnedDocsLock.Lock()
defer pinnedDocsLock.Unlock()
stored, err := readPinnedDocs()
if err != nil {
return err
}
selected := map[string]bool{}
refs := []pinnedDocRef{}
for _, id := range ids {
if !ast.IsNodeIDPattern(id) {
return fmt.Errorf("invalid document ID [%s]", id)
}
if selected[id] {
continue
}
selected[id] = true
if action == "unpin" {
continue
}
bt := treenode.GetBlockTree(id)
if bt != nil && IsEncryptedBox(bt.BoxID) {
return fmt.Errorf("%s", Conf.Language(396))
}
if !isPinnableDocument(bt) {
return fmt.Errorf("document [%s] cannot be pinned", id)
}
box := Conf.Box(bt.BoxID)
if box == nil || box.Stat(bt.Path) == nil {
return fmt.Errorf("document [%s] is unavailable", id)View on GitHub (pinned to 9f775e8a12)