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

  1. Check the wrapped cause for the actual OS error (ENOENT, EACCES, etc.) on the named attachment
  2. Ensure all assets referenced by the vault are present and fully downloaded (disable on-demand cloud placeholders)
  3. Free disk space in the system temp directory if it is full
  4. 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

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)