{"record":{"id":"29ba78a3cd230a9d","repo":"siyuan-note/siyuan","slug":"no-file-found","errorCode":null,"errorMessage":"no file found","messagePattern":"no file found","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/import.go","lineNumber":284,"sourceCode":"func stagedSYImportPath(token string) string {\n\treturn filepath.Join(stagedSYImportDir(), token+\".zip\")\n}\n\nfunc isValidSYImportToken(token string) bool {\n\tif len(token) != 32 {\n\t\treturn false\n\t}\n\tfor _, char := range token {\n\t\tif !(char >= 'a' && char <= 'z') && !(char >= 'A' && char <= 'Z') && !(char >= '0' && char <= '9') {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc saveImportUploadFile(c *gin.Context, file *multipart.FileHeader) (writePath string, cleanup func(), err error) {\n\tif file == nil {\n\t\treturn \"\", nil, errors.New(\"no file found\")\n\t}\n\n\timportDir := filepath.Join(util.TempDir, \"import\", gulu.Rand.String(7))\n\tif err = os.MkdirAll(importDir, 0755); err != nil {\n\t\treturn\n\t}\n\tcleanup = func() { _ = os.RemoveAll(importDir) }\n\twritePath = filepath.Join(importDir, filepath.Base(file.Filename))\n\tif !gulu.File.IsSubPath(importDir, writePath) {\n\t\terr = errors.New(\"import path is not sub path of import dir\")\n\t\tcleanup()\n\t\treturn\n\t}\n\n\tif err = c.SaveUploadedFile(file, writePath); err != nil {\n\t\tcleanup()\n\t}\n\treturn","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/import.go#L266-L302","documentation":"saveImportUploadFile extracts the uploaded file from a multipart request and rejects the call when the *multipart.FileHeader is nil with 'no file found'. The API contract expects exactly one uploaded file (e.g. the .sy zip) under the agreed form field.","triggerScenarios":"POSTing to the .sy upload endpoint with no 'file' form field, an empty part, wrong field name, or a client that sends JSON instead of multipart/form-data.","commonSituations":"fetch with Content-Type application/json instead of FormData; curl missing -F file=@...; the form field name mistyped (e.g. 'files' vs 'file'); proxies stripping empty file parts.","solutions":["Send multipart/form-data with the file under the exact expected field name.","With fetch: do NOT set Content-Type manually — new FormData with formData.append(\"file\", fileBlob, name).","With curl: curl -F \"file=@notebook.sy\" <endpoint>.","On the server, validate file != nil and return 400 with this message so clients can fix the request."],"exampleFix":"// before\nawait fetch(\"/api/import/syUpload\", {method:\"POST\", body: JSON.stringify({path})})\n\n// after\nconst fd = new FormData(); fd.append(\"file\", fileBlob, \"notebook.sy\")\nawait fetch(\"/api/import/syUpload\", {method:\"POST\", body: fd})","handlingStrategy":"validation","validationCode":"if (!(blob instanceof Blob) || blob.size === 0) throw new Error(\"attach a non-empty file before upload\")\nconst fd = new FormData(); fd.append(\"file\", blob, \"notebook.sy\")","typeGuard":"function hasFile(fd) { return fd.get(\"file\") instanceof File && fd.get(\"file\").size > 0 }","tryCatchPattern":"try { await upload(fd) }\ncatch (e) { if (e.message === \"no file found\") notify(\"Select a .sy file to upload\") else throw e }","preventionTips":["Always use FormData for this endpoint; never JSON bodies","Use the exact expected form field name","Let fetch set Content-Type automatically (multipart boundary)"],"tags":["import","upload","multipart","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}