siyuan-note/siyuan · error

%s

Error message

%s

What it means

This is the generic failure path of beginBlockToolScope: the internal fail helper releases the held notebook read lock and returns a formatted error with whatever message the surrounding block tool supplied. The message is dynamic ("%s"), so the actual text comes from the specific validation that called fail() — typically a per-block-ID check (e.g. block not found) inside the scope setup loop.

Source

Thrown at kernel/mcp/tools/block.go:651

	release = func() {}
	notebook, _ := args["notebook"].(string)
	notebook = strings.TrimSpace(notebook)
	encrypted := notebook != "" && model.IsEncryptedBox(notebook)
	if encrypted {
		model.HoldBoxReadLock(notebook)
		if !model.IsBoxUnlocked(notebook) {
			model.ReleaseBoxReadLock(notebook)
			return "", release, fmt.Errorf("encrypted notebook is locked, please unlock it first")
		}
		release = func() {
			model.ReleaseBoxReadLock(notebook)
		}
		boxID = notebook
	}

	fail := func(format string, values ...any) (string, func(), error) {
		release()
		return "", func() {}, fmt.Errorf(format, values...)
	}
	for _, id := range ids {
		if id == "" {
			continue
		}
		queryBoxID := ""
		if encrypted {
			queryBoxID = notebook
		}
		bt := treenode.GetBlockTreeInExactBox(id, queryBoxID)
		if bt == nil || (notebook != "" && bt.BoxID != notebook) {
			if notebook == "" {
				return fail("block %s was not found in a normal notebook; provide notebook for encrypted targets", id)
			}
			return fail("block %s does not belong to notebook %s", id, notebook)
		}
		if mutation && encrypted {
			if treenode.GetBlockTreeInExactBox(id, "") != nil {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Read the concrete error text returned after the %s substitution and address the underlying cause (usually a missing/invalid block ID).
  2. Verify the block ID exists (e.g. via SQL or blockGet on a known-good ID) before calling the tool.
  3. Refresh any cached IDs from your client; blocks may have been removed or their documents changed.
  4. Trim/normalize IDs on the caller side to avoid lookup failures.
Defensive patterns

Strategy: try-catch

Validate before calling

const known = await queryBlockIDs(); // e.g. via SQL tool
for (const id of ids) {
  if (!/^[0-9]{14}-[a-z0-9]{7}$/.test(id || '')) throw new Error('bad id: ' + id);
  if (!known.has(id)) throw new Error('unknown block id: ' + id);
}

Type guard

function isBlockID(v) { return typeof v === 'string' && /^[0-9]{14}-[a-z0-9]{7}$/.test(v); }

Try / catch

try {
  await mcp.call("blockInsert", { ... });
} catch (e) {
  // message is dynamic: log it fully, then validate/re-fetch the offending ID
  console.error(e.message); // e.g. "block not found: 2024...-xxx"
}

Prevention

When it happens

Trigger: Any of the block tools using beginBlockScope fail() during setup: an empty/invalid id resolution, a block ID that cannot be located, or other per-ID validation inside the ids loop of the scope builder.

Common situations: MCP callers passing block IDs that do not exist (deleted or from another notebook); IDs with surrounding whitespace that fail lookup; stale IDs cached by an agent after document changes.

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/19424a69ac8b1dd8. Report an issue: GitHub.