{"record":{"id":"067e7ed2f2c40c68","repo":"siyuan-note/siyuan","slug":"348","errorCode":"348","errorMessage":"copy attachment [%s]: %w","messagePattern":"copy attachment \\[(.+?)\\]: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":1706,"sourceCode":"\t\tfailObsidianTask(taskID, \"staging\", err, nil)\n\t\tremoveObsidianTemp(taskID)\n\t\treturn\n\t}\n\tvar assetKeys []string\n\tfor key := range vault.ImportAssets {\n\t\tassetKeys = append(assetKeys, key)\n\t}\n\tsort.Strings(assetKeys)\n\tfor index, key := range assetKeys {\n\t\tif err := ctx.Err(); err != nil {\n\t\t\tremoveObsidianTemp(taskID)\n\t\t\treturn\n\t\t}\n\t\tasset := vault.ImportAssets[key]\n\t\tdestination := filepath.Join(assetsTemp, asset.FinalName)\n\t\tif err := copyStableObsidianFile(asset.Source, destination); err != nil {\n\t\t\tfailObsidianTask(taskID, \"staging\", newObsidianUserError(348, asset.Source.RelPath,\n\t\t\t\tfmt.Errorf(\"copy attachment [%s]: %w\", asset.Source.RelPath, err)), nil)\n\t\t\tremoveObsidianTemp(taskID)\n\t\t\treturn\n\t\t}\n\t\tresult.ImportedAttachmentCount++\n\t\tupdateObsidianTask(taskID, ObsidianTaskStateStaging, 65+(index+1)*15/maxInt(len(assetKeys), 1), \"Copying attachments\")\n\t}\n\tif err := writeObsidianSortFile(tempRoot, vault.Docs); err != nil {\n\t\tfailObsidianTask(taskID, \"staging\", err, nil)\n\t\tremoveObsidianTemp(taskID)\n\t\treturn\n\t}\n\tif err := ctx.Err(); err != nil {\n\t\tremoveObsidianTemp(taskID)\n\t\treturn\n\t}\n\n\tif !updateObsidianTask(taskID, ObsidianTaskStateCreating, 82, \"Creating notebook\") {\n\t\tremoveObsidianTemp(taskID)","sourceCodeStart":1688,"sourceCodeEnd":1724,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L1688-L1724","documentation":"When staging an Obsidian import, each analyzed attachment (asset) is copied from the vault into the import's temporary assets directory via copyStableObsidianFile. Any copy failure is wrapped with the attachment's relative path as user error code 348; the task fails and temp files are removed.","triggerScenarios":"copyStableObsidianFile(asset.Source, destination) fails during the attachment-staging loop — source file missing or unreadable at copy time, destination not writable, or the file changed mid-copy.","commonSituations":"Asset was deleted/moved after analysis (attachment renamed in Obsidian during import); cloud-placeholder asset not downloaded; disk full in the temp location; AV software locking the file.","solutions":["Check the wrapped cause for the actual OS error (ENOENT, EACCES, etc.) on the named attachment","Ensure all assets referenced by the vault are present and fully downloaded (disable on-demand cloud placeholders)","Free disk space in the system temp directory if it is full","Pause sync/AV interference and re-run the import"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for _, a := range assets {\n    if _, err := os.Stat(a.absPath); err != nil { /* attachment missing before import */ }\n}\nif free, _ := diskFree(os.TempDir()); free < needed { /* free space first */ }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    if os.IsNotExist(errors.Unwrap(err)) { /* re-run import to re-analyze assets */ }\n    if os.IsPermission(errors.Unwrap(err)) { /* check temp dir / vault ACLs */ }\n}","preventionTips":["Ensure cloud assets are fully downloaded (no on-demand placeholders)","Keep enough free space in the temp directory","Exclude the vault from AV real-time locking during import"],"tags":["go","obsidian-import","file-copy"],"backgroundTag":"file-write-failed","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"}