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
- Append a .html or .htm extension to the name argument before calling the tool
- Use the default by passing an empty name (the tool substitutes "component.html") if no specific name is needed
- 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
- Always pass a filename with .html/.htm extension, or pass empty name for the default
- Do not rely on the tool to append extensions
- Remember only the base name's extension is checked — directories are stripped
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
- attr must be a string or null (got %T)
- Cannot create a file starting with .
- Conf.Language(151)
- Conf.Language(151)
- Conf.Language(151) (localized invalid filename message)
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)