siyuan-note/siyuan · error
invalid asset filename
Error message
invalid asset filename
What it means
After sanitizing the file name (util.FilterUploadFileName) and normalizing the extension to lowercase, InsertAssetBytes requires a non-empty base name and a non-empty extension. If the sanitized name collapses to empty/'.' or the file has no extension, it refuses to build the asset path, because assets are addressed by name+extension and an extension-less name would produce ambiguous/broken asset references.
Solutions
- Always pass a file name with a proper extension, e.g. 'diagram.png' instead of 'diagram'
- Inspect what FilterUploadFileName strips: pre-clean the name of characters it removes so a non-empty base survives
- Default a sensible extension in the caller when the source has none (e.g. derive from MIME type)
- Validate the name in the caller: non-empty base, non-empty '.'-prefixed ext before calling
Example fix
// before
model.InsertAssetBytes(id, "image", data)
// after
name := "image"
if filepath.Ext(name) == "" {
name += ".png" // derive from actual MIME type when possible
}
model.InsertAssetBytes(id, name, data) Defensive patterns
Strategy: validation
Validate before calling
base := filepath.Base(fileName)
if base == "" || base == "." || filepath.Ext(base) == "" {
return errors.New("asset file name must include an extension")
} Type guard
func hasNameAndExt(name string) bool {
b := filepath.Base(name)
return b != "" && b != "." && filepath.Ext(b) != ""
} Try / catch
assetPath, _, err := model.InsertAssetBytes(id, fileName, data)
if err != nil && strings.Contains(err.Error(), "invalid asset filename") {
fileName = ensureExtension(fileName) // e.g. append from MIME type, retry once
} Prevention
- Always generate asset names with an extension ('screenshot.png', not 'screenshot')
- Avoid characters stripped by FilterUploadFileName in programmatic names
- Derive extensions from MIME type when the source name is unknown
When it happens
Trigger: Calling InsertAssetBytes with fileName that has no extension ('image', '.hidden' cases where ext resolves empty), a name made entirely of characters stripped by FilterUploadFileName (illegal/path characters), or an empty fileName string.
Common situations: Generating assets with a programmatic name like 'screenshot' and forgetting '.png'; temp-file names or names derived from URLs without decoding to a file name; names containing only reserved characters on the target OS.
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
- asset data is empty
- 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/999103b4cef6c30a.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/upload.go:55
"github.com/siyuan-note/siyuan/kernel/util"
)
// InsertAssetBytes 将内存中的资源直接写入目标文档资源目录,避免生成内容经过明文临时文件。
func InsertAssetBytes(id, fileName string, data []byte) (assetPath string, created bool, err error) {
bt := treenode.GetBlockTree(id)
if bt == nil {
return "", false, errors.New(Conf.Language(71))
}
if len(data) == 0 {
return "", false, errors.New("asset data is empty")
}
baseName := filepath.Base(fileName)
fName := util.FilterUploadFileName(baseName)
ext := strings.ToLower(filepath.Ext(fName))
fName = strings.TrimSuffix(fName, filepath.Ext(fName)) + ext
if fName == "" || fName == "." || ext == "" {
return "", false, errors.New("invalid asset filename")
}
docDirLocalPath := filepath.Join(util.DataDir, bt.BoxID, path.Dir(bt.Path))
assetsDirPath := getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), docDirLocalPath)
if err = os.MkdirAll(assetsDirPath, 0755); err != nil {
return "", false, err
}
reader := bytes.NewReader(data)
hash, err := util.GetEtagByHandle(reader, int64(len(data)))
if err != nil {
return "", false, err
}
if existAssetPath := GetAssetPathByHash(hash, bt.BoxID); existAssetPath != "" {
originalName := assetNameWithoutID(filepath.Base(existAssetPath))
if strings.EqualFold(assetNameWithoutID(fName), originalName) {
return strings.TrimPrefix(existAssetPath, "/"), false, nil
}View on GitHub (pinned to 9f775e8a12)