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
- 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
- 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
- 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
- Clean up leftover notebooks/assets from earlier failed imports before re-importing
- Avoid importing multiple vaults containing identically named attachments into one workspace
- Rename generic attachment names (image.png) in the vault beforehand
- Check result.Incomplete after any import
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
- Query asset failed [%s]
- 350
- source is not an encrypted asset
- --path must not be empty
- --path is required
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/24f6481fccef8aa5.
Report an issue: GitHub.