siyuan-note/siyuan · error

invalid attribute view source

Error message

invalid attribute view source [%d]: isDetached must be a boolean

What it means

In the same batch validation, if an entry contains an isDetached key whose value is not a boolean (and not absent), the batch is rejected with this error. Serialized bindings may omit isDetached (treated as false), but an explicitly present non-boolean value — e.g. the string "false" or the number 0 — is invalid.

Solutions

  1. Coerce isDetached to a real boolean (Boolean(value) or === comparison) before sending
  2. Omit the key entirely when the value is false, matching the omission-tolerant format
  3. Fix the client serializer to preserve JSON boolean types

Example fix

// before
{ id: blockID, isDetached: "true" }
// after
{ id: blockID, isDetached: true }
Defensive patterns

Strategy: validation

Validate before calling

if ('isDetached' in src && typeof src.isDetached !== 'boolean') {
  throw new Error('isDetached must be boolean');
}

Type guard

function hasBooleanIsDetached(s) {
  return !('isDetached' in s) || typeof s.isDetached === 'boolean';
}

Try / catch

try {
  await api.bindSources(srcs);
} catch (e) {
  if (/isDetached must be a boolean/.test(e.message)) {
    srcs = srcs.map(s => ({ ...s, isDetached: Boolean(s.isDetached) }));
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling the batch bind API with srcs[i].isDetached set to a non-boolean such as "true", 1, or null-derived values from a loosely typed client.

Common situations: JavaScript clients sending stringified booleans from form inputs; payloads round-tripped through CSV/JSON where booleans became strings; hand-written API calls.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/80ce821af9b08642. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view.go:6076

		return
	}
	slices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286
	attrView, err := avParseView(avID, dbBlockID)
	if err != nil {
		return
	}

	// 整批校验完成后再写入,避免后续条目越界时已修改前面的绑定。
	boundTrees := map[string]*parse.Tree{}
	detachedSources := make([]bool, len(srcs))
	for index, src := range srcs {
		if nil == src {
			return result, fmt.Errorf("invalid attribute view source [%d]", index)
		}
		// 绑定条目的序列化数据可能省略值为 false 的字段,撤销重放时按绑定状态读取。
		isDetached, ok := src["isDetached"].(bool)
		if !ok && nil != src["isDetached"] {
			return result, fmt.Errorf("invalid attribute view source [%d]: isDetached must be a boolean", index)
		}
		detachedSources[index] = isDetached
		if isDetached {
			continue
		}
		id, ok := src["id"].(string)
		if !ok {
			return result, fmt.Errorf("invalid attribute view source [%d]: id must be a string", index)
		}
		if !ast.IsNodeIDPattern(id) {
			continue
		}
		var tree *parse.Tree
		if tx != nil {
			tree, err = tx.loadTree(id)
		} else {
			tree, err = LoadTreeByBlockID(id)
		}

View on GitHub (pinned to 9f775e8a12)