siyuan-note/siyuan · error

350

350

Error message

global attachment [%s] already exists

What it means

In the writing phase each staged attachment is renamed from temp into the global <workspace>/assets/ directory; if filelock.IsExist(destination) is true the task aborts with 'global attachment [name] already exists' (i18n 350), the notebook is marked incomplete via markObsidianNotebookIncomplete, and failObsidianTask records it at the 'writing' stage. The final name is the importer-computed FinalName, so this means a file with that exact name already occupies the global assets slot.

Source

Thrown at kernel/model/import_obsidian.go:1756

		markObsidianNotebookIncomplete(boxID, notebookName)
		failObsidianTask(taskID, "writing", err, result)
		removeObsidianTemp(taskID)
		return
	}
	if err = os.MkdirAll(filepath.Join(util.DataDir, "assets"), 0755); err != nil {
		result.Incomplete = true
		markObsidianNotebookIncomplete(boxID, notebookName)
		failObsidianTask(taskID, "writing", err, result)
		removeObsidianTemp(taskID)
		return
	}

	for _, key := range assetKeys {
		asset := vault.ImportAssets[key]
		source := filepath.Join(assetsTemp, asset.FinalName)
		destination := filepath.Join(util.DataDir, "assets", asset.FinalName)
		if filelock.IsExist(destination) {
			err = fmt.Errorf("global attachment [%s] already exists", asset.FinalName)
		} else {
			err = filelock.Rename(source, destination)
		}
		if err != nil {
			result.Incomplete = true
			markObsidianNotebookIncomplete(boxID, notebookName)
			failObsidianTask(taskID, "writing", err, result)
			removeObsidianTemp(taskID)
			return
		}
	}

	updateObsidianTask(taskID, ObsidianTaskStateIndexing, 95, "Indexing notebook")
	existed, err := Mount(boxID)
	if err != nil {
		result.Incomplete = true
		markObsidianNotebookIncomplete(boxID, notebookName)
		failObsidianTask(taskID, "indexing", err, result)

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Remove or rename the conflicting file(s) under the workspace data/assets/ directory named in the error, then retry the import
  2. If a prior import of the same vault already moved these attachments, either reuse that notebook or delete the earlier notebook and its assets before re-importing
  3. Check whether a previous import task was left half-finished; clean its assets and re-analyze
  4. If the collision is between two distinct vaults, rename one vault's attachment before import
Defensive patterns

Strategy: validation

Validate before calling

// Before confirming import, assert none of the planned FinalNames collide
for _, name := range plannedFinalNames {
    if filelock.IsExist(filepath.Join(util.DataDir, "assets", name)) {
        return fmt.Errorf("assets/%s already exists; remove or rename it first", name)
    }
}

Type guard

func isIncompleteNotebookWrite(err error) bool {
	var ue *obsidianUserError
	return errors.As(err, &ue) && ue.DetailLanguage == 350
}

Try / catch

// Not retryable as-is: clean the colliding entries under data/assets,
// remove the incomplete notebook, then restart the vault import.

Prevention

When it happens

Trigger: Importing a vault whose attachment resolved name collides with an existing file in the workspace assets folder - most commonly residue from an earlier import of the same vault, an earlier attempt that failed midway after renaming some assets, or genuinely identical attachment filenames across different sources.

Common situations: Re-importing the same vault after a previous partial/failed run (assets already moved into data/assets); importing two different vaults that contain same-named attachments; a previous import cancelled during the writing phase leaving half-moved files.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/63b488d7f2491452. Report an issue: GitHub.