siyuan-note/siyuan · error
348
348
Error message
copy attachment [%s]: %w
What it means
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.
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
Defensive patterns
Strategy: validation
Validate before calling
for _, a := range assets {
if _, err := os.Stat(a.absPath); err != nil { /* attachment missing before import */ }
}
if free, _ := diskFree(os.TempDir()); free < needed { /* free space first */ } Try / catch
if err != nil {
if os.IsNotExist(errors.Unwrap(err)) { /* re-run import to re-analyze assets */ }
if os.IsPermission(errors.Unwrap(err)) { /* check temp dir / vault ACLs */ }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/067e7ed2f2c40c68.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/import_obsidian.go:1706
failObsidianTask(taskID, "staging", err, nil)
removeObsidianTemp(taskID)
return
}
var assetKeys []string
for key := range vault.ImportAssets {
assetKeys = append(assetKeys, key)
}
sort.Strings(assetKeys)
for index, key := range assetKeys {
if err := ctx.Err(); err != nil {
removeObsidianTemp(taskID)
return
}
asset := vault.ImportAssets[key]
destination := filepath.Join(assetsTemp, asset.FinalName)
if err := copyStableObsidianFile(asset.Source, destination); err != nil {
failObsidianTask(taskID, "staging", newObsidianUserError(348, asset.Source.RelPath,
fmt.Errorf("copy attachment [%s]: %w", asset.Source.RelPath, err)), nil)
removeObsidianTemp(taskID)
return
}
result.ImportedAttachmentCount++
updateObsidianTask(taskID, ObsidianTaskStateStaging, 65+(index+1)*15/maxInt(len(assetKeys), 1), "Copying attachments")
}
if err := writeObsidianSortFile(tempRoot, vault.Docs); err != nil {
failObsidianTask(taskID, "staging", err, nil)
removeObsidianTemp(taskID)
return
}
if err := ctx.Err(); err != nil {
removeObsidianTemp(taskID)
return
}
if !updateObsidianTask(taskID, ObsidianTaskStateCreating, 82, "Creating notebook") {
removeObsidianTemp(taskID)View on GitHub (pinned to 9f775e8a12)