siyuan-note/siyuan · error
Content block with id
Error message
Content block with id [%s] not found
What it means
Conf.Language(15) renders as 'Content block with id [%s] not found'. GetHeadingDeleteTransaction loads the document tree via LoadTreeByBlockID(id) — which succeeded — but treenode.GetNodeInTree(tree, id) then fails to find the node with that ID inside the loaded tree. This means the blocktree index still maps the ID to a document, but the document content no longer contains it: a stale index or the block was deleted/renumbered since indexing. Returned by POST /api/block/getHeadingDeleteTransaction with code=-1 and closeTimeout=7000.
Solutions
- Re-fetch the current block ID (e.g. via /api/query/SQL or /api/block/getBlockInfo) and retry with a fresh ID
- If IDs are persistently missing, rebuild the index (Settings - Search - Rebuild index, or the doc tree rebuild option)
- Flush pending transactions before relying on IDs right after heavy edits
- Handle the error gracefully in the UI (this is what closeTimeout=7000 in the response hints at)
Example fix
// before
const tx = await fetchSyncPost("/api/block/getHeadingDeleteTransaction", {id: cachedHeadingID})
// after: refresh the ID before requesting the delete transaction
const rows = await fetchSyncPost("/api/query/sql", {stmt: `select id from blocks where id = '${cachedHeadingID}'`})
if (rows.data.length === 0) {
// block gone: drop the stale reference
return
}
const tx = await fetchSyncPost("/api/block/getHeadingDeleteTransaction", {id: cachedHeadingID}) Defensive patterns
Strategy: validation
Validate before calling
// Verify the block still exists before building a heading-delete transaction
rows := sqlQuery(fmt.Sprintf("select id from blocks where id = '%s'", id))
if len(rows) == 0 { return fmt.Errorf("block %s no longer exists", id) }
transaction, err := model.GetHeadingDeleteTransaction(id) Type guard
func blockExists(id string) bool {
rows, err := model.QuerySQL(fmt.Sprintf("select id from blocks where id = '%s'", id))
return nil == err && 0 < len(rows)
} Try / catch
transaction, err := model.GetHeadingDeleteTransaction(id)
if err != nil {
if strings.Contains(err.Error(), "not found") {
// stale ID: drop cached references and refresh the doc; do not blindly retry the same ID
return refreshAndRetryOnce(id)
}
return err
} Prevention
- Never cache heading IDs across editing sessions; re-query at action time
- After sync or heavy edits, rebuild the index before operating on captured IDs
- Treat 'not found' from heading transaction APIs as a signal to refresh, not as a hard failure
When it happens
Trigger: POST /api/block/getHeadingDeleteTransaction with a heading ID that was deleted in the meantime (edited in another window/device, synced change, or refactored by a plugin) while the blocktree index was not yet updated; also an ID that only exists in a different document than the tree it resolved to.
Common situations: Stale block IDs cached by frontend code or plugins; concurrent edits via sync or multiple windows; index not rebuilt after data rollback or manual workspace surgery; passing a document ID where a heading ID is required in rare resolution edge cases.
Related errors
- get block failed
- block not found
- block not found or its encrypted notebook is locked
- get block tree by id
- load tree failed
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/9b5fcfe33b85f7ce.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/block.go:843
if !sameTree {
if err = indexWriteTreeUpsertQueue(defTree); err != nil {
return
}
}
FlushTxQueue()
util.ReloadUI()
return
}
func GetHeadingDeleteTransaction(id string) (transaction *Transaction, err error) {
tree, err := LoadTreeByBlockID(id)
if err != nil {
return
}
node := treenode.GetNodeInTree(tree, id)
if nil == node {
err = fmt.Errorf(Conf.Language(15), id)
return
}
if ast.NodeHeading != node.Type {
return
}
var nodes []*ast.Node
nodes = append(nodes, node)
nodes = append(nodes, treenode.HeadingChildren(node)...)
hiddenNodes := map[string]bool{}
for _, hidden := range treenode.CollectFoldHiddenNodes(node.Parent) {
hiddenNodes[hidden.ID] = true
}
transaction = &Transaction{}
luteEngine := util.NewLute()
for _, n := range nodes {View on GitHub (pinned to afa823b6b4)