siyuan-note/siyuan · warning

global attachment [%s] already exists

Error message

global attachment [%s] already exists

What it means

Raised during the final move of staged attachments into the workspace's global assets folder (data/assets). If a file with the same final name already exists there, the import does not overwrite it; instead it marks the result incomplete and records this conflict error so the notebook is flagged as needing manual attention.

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 8641553a1f)

Solutions

  1. Inspect data/assets for the reported FinalName; if it is from a prior import of the same vault, delete the leftover imported notebook and assets, then re-import cleanly
  2. If the collision is with unrelated content, rename the source attachment in the vault (or let the importer generate a fresh unique name) and re-import
  3. Treat the imported notebook as incomplete: verify which attachments are missing and copy the conflicting file manually if content differs
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('fs');
function assertNoAssetCollisions(dataDir, finalNames) {
  const assetsDir = require('path').join(dataDir, 'assets');
  const clashes = finalNames.filter(n => fs.existsSync(require('path').join(assetsDir, n)));
  if (clashes.length) throw new Error('will collide: ' + clashes.join(', '));
}

Try / catch

try { await apiImportObsidian(vaultPath); }
catch (e) {
  if (/already exists/.test(e.msg)) {
    // inspect data/assets/<name>; remove stale files from a previous import or rename source asset, retry
  }
}

Prevention

When it happens

Trigger: filelock.IsExist(destination) is true for filepath.Join(util.DataDir, "assets", asset.FinalName) — an attachment with an identical resolved filename was imported (by this or a previous import) before the rename step runs.

Common situations: Re-importing the same vault (or two vaults containing identically named images like 'image.png') into the same workspace; name-collision resolution during transformation produced a FinalName that already exists from an earlier partial import.

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@8641553a1f (2026-09-11). Data as JSON: /api/errors/24f6481fccef8aa5. Report an issue: GitHub.