{"record":{"id":"013215a305918af8","repo":"siyuan-note/siyuan","slug":"w-during-analysis","errorCode":null,"errorMessage":"%w during analysis","messagePattern":"%w during analysis","errorType":"exception","errorClass":"errObsidianSourceChanged","httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":932,"sourceCode":"\t\t\t}\n\t\t}\n\t\ttree, _, _, _ := parseStdMd(data)\n\t\tif tree == nil {\n\t\t\treturn newObsidianUserError(347, doc.Source.RelPath,\n\t\t\t\tfmt.Errorf(\"parse Markdown [%s] failed\", doc.Source.RelPath))\n\t\t}\n\t\tanalyzeObsidianMarkdownLinks(vault, doc, tree)\n\t}\n\treturn nil\n}\n\nfunc readStableObsidianFile(file *obsidianSourceFile) ([]byte, error) {\n\tbefore, err := os.Stat(file.AbsPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif before.Size() != file.Size || !before.ModTime().Equal(file.ModTime) {\n\t\treturn nil, fmt.Errorf(\"%w during analysis\", errObsidianSourceChanged)\n\t}\n\tdata, err := os.ReadFile(file.AbsPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tafter, err := os.Stat(file.AbsPath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif before.Size() != after.Size() || !before.ModTime().Equal(after.ModTime()) {\n\t\treturn nil, fmt.Errorf(\"%w while reading\", errObsidianSourceChanged)\n\t}\n\treturn data, nil\n}\n\nfunc newObsidianReadUserError(file *obsidianSourceFile, err error) error {\n\tlanguage := 345\n\tif errors.Is(err, errObsidianSourceChanged) {","sourceCodeStart":914,"sourceCodeEnd":950,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/import_obsidian.go#L914-L950","documentation":"A Vault file changed between the initial scan and the moment it was read for analysis. readStableObsidianFile stats the file before reading and compares size and modification time against the values captured during the scan; a mismatch is wrapped in errObsidianSourceChanged with the context 'during analysis', so callers can detect it with errors.Is. This guarantees the import operates on a stable snapshot.","triggerScenarios":"os.Stat(file.AbsPath) succeeds but before.Size() != file.Size or !before.ModTime().Equal(file.ModTime) — the file was modified (or replaced) after scanObsidianVaultFiles recorded its metadata.","commonSituations":"Obsidian (or another editor) is open and autosaves during the import; a sync client (iCloud/Dropbox/Syncthing) updates files concurrently; backup tools touch mtimes.","solutions":["Close Obsidian and any other editors, then re-run the import","Pause cloud-sync/backup tools that touch the vault during import","Make a static copy of the vault and import the copy","Re-run the import — a stable vault will pass the before-stat check"],"exampleFix":"// before: importing a live vault\nimportFrom(vaultDir)\n// after: import a quiescent snapshot\ncp -r vaultDir /tmp/vault-snapshot\nimportFrom(\"/tmp/vault-snapshot\")","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await importObsidianVault(vaultPath);\n} catch (e) {\n  if (errors.Is(e, errObsidianSourceChanged)) {\n    await retryImport(vaultPath); // after quiescing editors/sync\n  }\n}","preventionTips":["Close Obsidian and pause sync/backup tools during import","Import from a frozen snapshot copy of the vault","Disable autosave temporarily if the vault is actively edited"],"tags":["obsidian-import","concurrency","file-changed","stability-check"],"backgroundTag":"checksum-mismatch","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}