siyuan-note/siyuan · error

name must end in .html or .htm

Error message

name must end in .html or .htm

What it means

normalizeHTMLAssetName validates that the requested asset filename has an .html or .htm extension. The name is reduced to its base path and its lowercase extension is checked; anything else is rejected so only HTML components can be created by assetCreateHTML.

Solutions

  1. Append a .html or .htm extension to the name argument before calling the tool
  2. Use the default by passing an empty name (the tool substitutes "component.html") if no specific name is needed
  3. Strip any directory prefix yourself — only the base name's extension matters

Example fix

// before
assetCreateHTML(name: "my-widget")
// after
assetCreateHTML(name: "my-widget.html")
Defensive patterns

Strategy: validation

Validate before calling

func validHTMLAssetName(name string) bool {
    ext := strings.ToLower(filepath.Ext(filepath.Base(name)))
    return ext == ".html" || ext == ".htm"
}

Prevention

When it happens

Trigger: assetCreateHTML is called with a name argument that is empty-derived, a bare filename without extension, or ending in another extension (.txt, .HTML is fine after lowering, .md is rejected).

Common situations: Callers passing a title/label instead of a filename; omitting the extension assuming the tool appends one; passing a path whose base name lacks the extension (extension in a directory part).

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at kernel/mcp/tools/asset.go:176

		Content: []ContentItem{{Type: "text", Text: fmt.Sprintf("Created HTML IFrame block: %s\nAsset: %s", blockID, assetPath)}},
		StructuredContent: map[string]any{
			"blockID":   blockID,
			"assetPath": assetPath,
		},
		StructuredContentSet: true,
	}, nil
}

func normalizeHTMLAssetName(value any) (string, error) {
	name, _ := value.(string)
	name = strings.TrimSpace(name)
	if name == "" {
		name = "component.html"
	}
	name = filepath.Base(name)
	ext := strings.ToLower(filepath.Ext(name))
	if ext != ".html" && ext != ".htm" {
		return "", fmt.Errorf("name must end in .html or .htm")
	}
	return name, nil
}

func htmlAssetIFrameBlockDOM(assetPath string) (dom, blockID string, err error) {
	src := html.EscapeString(model.HTMLAssetIFrameSrc(assetPath))
	dom, err = markdownToBlockDOM(`<iframe sandbox="allow-scripts" src="` + src + `" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>`)
	if err != nil {
		return
	}
	tree := util.NewLute().BlockDOM2Tree(dom)
	if tree == nil || tree.Root == nil || tree.Root.FirstChild == nil {
		return "", "", fmt.Errorf("empty IFrame block")
	}
	blockID = tree.Root.FirstChild.ID
	return
}

View on GitHub (pinned to 9f775e8a12)