siyuan-note/siyuan · error

attribute [%s] is only supported on tab containers

Error message

attribute [%s] is only supported on tab containers

What it means

ValidateTabsAttrs enforces that the tab-specific attributes (TabsActiveIDAttr and TabsPositionAttr) are only set on nodes whose AST type is NodeTabs. Setting either attribute on any other node type — or on a nil node — aborts with this error, leaving attributes unchanged.

Source

Thrown at kernel/treenode/tabs.go:49

		if nil == ret {
			ret = item
		}
		if item.ID == activeID {
			return item
		}
	}
	return
}

// ValidateTabsAttrs 在修改属性前校验页签属性,避免部分属性已写入后才发现错误。
func ValidateTabsAttrs(node *ast.Node, attrs map[string]string) error {
	for name, value := range attrs {
		name = strings.ToLower(name)
		if TabsActiveIDAttr != name && TabsPositionAttr != name {
			continue
		}
		if nil == node || ast.NodeTabs != node.Type {
			return fmt.Errorf("attribute [%s] is only supported on tab containers", name)
		}
		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
			}
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Verify the target node type is a tab container (NodeTabs) before setting these attributes, e.g. via the block's type field
  2. Remove or rename the attribute if it was not intended as a tabs directive
  3. If the node cannot be resolved (nil), re-fetch the correct block ID first

Example fix

// before
setNodeAttrs(blockID, { "tabs-position": "left" }); // block is a paragraph
// after: only set on tab containers
const attrs = await getBlockAttrs(blockID);
if (block.type === "tabs") {
  setNodeAttrs(blockID, { "tabs-position": "left" });
}
Defensive patterns

Strategy: type-guard

Validate before calling

const block = await getBlockByID(blockID);
if (block.type !== "tabs" && ("tabs-position" in attrs || "tabs-active-id" in attrs)) {
  throw new Error("tabs attributes require a tabs container");
}

Type guard

const isTabsContainer = (b) => b.type === "tabs";

Try / catch

try {
  await setNodeAttrs(blockID, attrs);
} catch (e) {
  if (String(e.message).includes("only supported on tab containers")) {
    console.warn("skipped tabs attr on non-tab node", blockID);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling setNodeAttrs0 (the set node attributes API) with an attribute named like the tabs active-id or tabs-position attribute on a non-tab block, or on a block whose node could not be resolved (nil).

Common situations: Plugins bulk-applying attributes without checking node type; typos in attribute names that collide with the reserved tabs attribute names; scripts written against tabs UI copying attributes onto ordinary containers.

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


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