siyuan-note/siyuan · error

Conf.Language(112), invalidChar

Error message

Conf.Language(112), invalidChar

What it means

RenameTag validates the new tag label with treenode.ContainsMarker before performing the rename. ContainsMarker (kernel/treenode/marker.go:24) rejects ASCII strings containing marker characters (characters that have special meaning in Markdown/attribute syntax), and RenameTag returns fmt.Errorf(Conf.Language(112), invalidChar) — a localized message with the offending character interpolated. The error means the requested new tag label contains a reserved marker character and cannot be used.

Solutions

  1. Remove the marker character (e.g. '#', '/', '[]') from the new label and retry the rename.
  2. Strip leading/trailing '/' and whitespace yourself before calling, since the marker check runs before trimming.
  3. Sanitize programmatically: filter out ASCII marker characters from the label before invoking RenameTag.
  4. Use the localized message text (Language(112)) which names the offending character to guide the user.

Example fix

// before: raw user input passed straight to the API
await fetchPost("/api/format/renameTag", {oldLabel: oldTag, newLabel: input});

// after: reject marker characters up front
const markerChars = /[\x20-\x7e]/; // combined with the kernel's marker set
if (/[#\/[\]()!`*~_>-]/.test(input)) {
    showMessage("The new tag name contains an invalid character (e.g. # or /)");
} else {
    await fetchPost("/api/format/renameTag", {oldLabel: oldTag, newLabel: input.trim().replace(/^\/|\/$/g, "")});
}
Defensive patterns

Strategy: validation

Validate before calling

// reject marker characters in the new tag label before calling the API
function isValidTagLabel(label) {
  if (!/^\x00-\x7f*$/.test(label)) return true; // non-ASCII bypasses kernel check
  return !/[#\/[\]()!`*~_{}>'"~@+$%^&|=;:,?<\\ -]/.test(label.replace(/^\/|\/$/g, "").trim());
}
if (!isValidTagLabel(newLabel)) throw new Error("tag name contains an invalid marker character");

Type guard

function hasMarkerChar(label) {
  if (!label || !/^[-\x7f]*$/.test(label)) return false; // non-ASCII is allowed by the kernel
  const markers = "#/[]()!`*~_-{}>\"'"; // mirror treenode.IsMarker set
  return [...label].some(c => markers.includes(c));
}

Try / catch

try {
  await fetchPost("/api/format/renameTag", {oldLabel, newLabel});
} catch (e) {
  showMessage(e.message); // localized: names the offending character
}

Prevention

When it happens

Trigger: Calling RenameTag (API /api/format/tagRenameContract, or the tag panel rename dialog) with newLabel containing an ASCII marker character such as #, /, [, ], (, ), or other IsMarker characters. Non-ASCII input bypasses the check (ContainsMarker returns "" for non-ASCII strings), so this fires specifically for ASCII labels with marker chars. Note the check runs on the raw newLabel, before trimming surrounding '/' and whitespace.

Common situations: User pastes a Markdown heading like '#todo' or a hierarchical path '/work/project/' intending slash hierarchy; scripts generating tags from file names or issue titles containing brackets or parentheses; batch-rename automation that doesn't sanitize labels.

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@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/30493412f29f2727. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/tag.go:128

	indexHistoryDir(filepath.Base(historyDir), util.NewLute())
	sql.FlushQueue()

	reloadTreeIDs = gulu.Str.RemoveDuplicatedElem(reloadTreeIDs)
	for _, id := range reloadTreeIDs {
		ReloadProtyle(id)
	}

	updateAttributeViewBlockText(updateNodes)

	sql.FlushQueue()
	util.PushClearProgress()
	return
}

func RenameTag(oldLabel, newLabel string) (err error) {
	if invalidChar := treenode.ContainsMarker(newLabel); "" != invalidChar {
		return fmt.Errorf(Conf.Language(112), invalidChar)
	}

	newLabel = strings.TrimPrefix(newLabel, "/")
	newLabel = strings.TrimSuffix(newLabel, "/")
	newLabel = strings.TrimSpace(newLabel)

	if "" == newLabel {
		return errors.New(Conf.Language(114))
	}

	if oldLabel == newLabel {
		return
	}

	util.PushEndlessProgress(Conf.Language(110))
	util.RandomSleep(500, 1000)

	tags := sql.QueryTagSpansByLabel(oldLabel)

View on GitHub (pinned to 9f775e8a12)