siyuan-note/siyuan · error

active tab [%s] is not a direct child of [%s]

Error message

active tab [%s] is not a direct child of [%s]

What it means

When the tabs active-id attribute is set on a tab container, ValidateTabsAttrs walks the node's direct children looking for a NodeTabItem whose ID equals the given value. If no direct child matches, the attribute would point at a tab that is not an immediate child, so the update is rejected.

Source

Thrown at kernel/treenode/tabs.go:69

		value = strings.TrimSpace(util.RemoveInvalidRetainCtrl(value))
		if "" == value {
			continue
		}
		if TabsPositionAttr == name {
			if "top" != value && "left" != value {
				return fmt.Errorf("invalid tabs position [%s]", value)
			}
			continue
		}
		found := false
		for item := node.FirstChild; nil != item; item = item.Next {
			if ast.NodeTabItem == item.Type && item.ID == value {
				found = true
				break
			}
		}
		if !found {
			return fmt.Errorf("active tab [%s] is not a direct child of [%s]", value, node.ID)
		}
	}
	return nil
}

// NormalizeTabs 修复空页和无效选中项,保留所有已有内容及块标识。
func NormalizeTabs(root *ast.Node) (changed bool) {
	if nil == root {
		return
	}
	ast.Walk(root, func(node *ast.Node, entering bool) ast.WalkStatus {
		if entering {
			return ast.WalkContinue
		}
		if ast.NodeTabItem == node.Type {
			if !hasContentChild(node) {
				node.AppendChild(NewParagraph(""))
				changed = true

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Use the ID of a direct NodeTabItem child of the tabs container (not the content block inside it)
  2. Re-read the tabs container's children to get current, valid tab IDs before setting the attribute
  3. Remove the attribute or let NormalizeTabs repair the invalid selection if the tab was deleted

Example fix

// before
setNodeAttrs(tabsID, { "tabs-active-id": someDeepBlockID });
// after: pick an actual direct child tab item
const tabItemID = directChildTabItems(tabsID)[0].id;
setNodeAttrs(tabsID, { "tabs-active-id": tabItemID });
Defensive patterns

Strategy: validation

Validate before calling

const tabsNode = await getBlockByID(tabsID);
const childIDs = await getChildBlockIDs(tabsID); // direct children only
if (!childIDs.includes(activeTabID)) {
  throw new Error(`${activeTabID} is not a direct child of ${tabsID}`);
}

Prevention

When it happens

Trigger: Setting the tabs active-id attribute via setNodeAttrs0 to an ID that is not a direct NodeTabItem child of the target tabs node — e.g. a nested block inside a tab panel, a tab from another tabs container, or a stale/deleted tab ID.

Common situations: Plugins caching tab IDs that were later deleted or moved; confusing the content block inside a tab with the tab item itself; referencing tabs from a different container after refactoring.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/6a0385f11c45057d. Report an issue: GitHub.