{"record":{"id":"0aae26a4799f5bfa","repo":"siyuan-note/siyuan","slug":"obsidian-vault-config-directory-is-missing","errorCode":null,"errorMessage":"Obsidian Vault config directory is missing","messagePattern":"Obsidian Vault config directory is missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/import_obsidian.go","lineNumber":233,"sourceCode":"\treturn err.Cause.Error()\n}\n\nfunc (err *obsidianUserError) Unwrap() error {\n\treturn err.Cause\n}\n\nfunc newObsidianUserError(detailLanguage int, relPath string, cause error) error {\n\treturn &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}\n}\n\nvar (\n\tobsidianTasksMu                 sync.Mutex\n\tobsidianTasks                   = map[string]*obsidianTask{}\n\tobsidianActive                  string\n\terrObsidianVaultUnreadable      = errors.New(\"Obsidian Vault is unreadable\")\n\terrObsidianVaultNotDirectory    = errors.New(\"Obsidian Vault path is not a directory\")\n\terrObsidianVaultUnsafePath      = errors.New(\"Obsidian Vault path is unsafe\")\n\terrObsidianVaultConfigMissing   = errors.New(\"Obsidian Vault config directory is missing\")\n\terrObsidianVaultMarkdownMissing = errors.New(\"Obsidian Vault has no readable Markdown\")\n\terrObsidianSourceChanged        = errors.New(\"Obsidian source file changed\")\n\n\tobsidianBlockIDPattern  = regexp.MustCompile(`(?m)(?:^|[ \\t])\\^([A-Za-z0-9-]+)[ \\t]*$`)\n\tobsidianQuotePattern    = regexp.MustCompile(`^((?:[ \\t]*>[ \\t]?)+)(.*)$`)\n\tobsidianListItemPattern = regexp.MustCompile(`^([ \\t]*(?:[-+*]|\\d+[.)])[ \\t]+)(.*)$`)\n\tobsidianFootnotePattern = regexp.MustCompile(`(?m)\\[\\^[^\\]\\r\\n]+\\]`)\n)\n\nfunc StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {\n\tvar replacedTaskID string\n\tobsidianTasksMu.Lock()\n\tif obsidianActive != \"\" {\n\t\tif active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {\n\t\t\tif !isObsidianPreImportState(active.State) {\n\t\t\t\tobsidianTasksMu.Unlock()\n\t\t\t\treturn nil, errors.New(Conf.Language(329))\n\t\t\t}","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/import_obsidian.go#L215-L251","documentation":"A sentinel error returned when the .obsidian configuration subdirectory is missing or malformed inside the selected Vault root. validateObsidianVaultRoot stats `<vault>/.obsidian`: if it does not exist (os.IsNotExist), or if it exists but is not a directory or is itself a symlink/reparse point, this error is returned. It is also the error that distinguishes a genuine Obsidian Vault from an arbitrary Markdown folder. The obsidianVaultErrorLanguage helper maps it to i18n key 339.","triggerScenarios":"POST /api/import/startObsidianVaultAnalysis with a directory that has no .obsidian subfolder (i.e. it is not actually an Obsidian Vault), or where .obsidian is a file/symlink rather than a real directory.","commonSituations":"Selecting a plain folder of Markdown files that was never opened in Obsidian; selecting a folder that was an Obsidian Vault but the .obsidian dir was deleted or not copied; .obsidian is a symlink (some sync setups symlink it); the user is confused about which folder is the Vault root and picked a parent or child.","solutions":["Confirm the selected directory was opened in Obsidian at least once, which creates the .obsidian folder.","If migrating/copying the Vault, ensure hidden directories are included (.obsidian is hidden by default on Unix).","Select the immediate parent of the .obsidian folder as the Vault root — not a level above or below.","If .obsidian is a symlink, replace it with the actual directory or point at the real Vault location."],"exampleFix":"// before: plain md folder with no .obsidian\nlocalPath := \"/home/user/just-markdown\"\n// -> errObsidianVaultConfigMissing\n\n// after: use a real Obsidian Vault root\nlocalPath := \"/home/user/MyVault\" // contains /home/user/MyVault/.obsidian/","handlingStrategy":"validation","validationCode":"// Confirm the .obsidian config directory exists and is a real directory\nfunc ensureObsidianConfig(vaultDir string) error {\n    cfg := filepath.Join(vaultDir, \".obsidian\")\n    info, err := os.Lstat(cfg)\n    if err != nil { return fmt.Errorf(\".obsidian config missing: %w\", err) }\n    if !info.IsDir() { return errors.New(\".obsidian is not a directory\") }\n    if info.Mode()&os.ModeSymlink != 0 { return errors.New(\".obsidian is a symlink\") }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := model.StartObsidianVaultAnalysis(localPath); err != nil {\n    if errors.Is(err, errObsidianVaultConfigMissing) {\n        return errors.New(\"this folder is not an Obsidian Vault (no .obsidian config found); open it in Obsidian first\")\n    }\n}","preventionTips":["Open the folder in Obsidian at least once so .obsidian is created.","When copying a Vault, include hidden files so .obsidian comes along.","Select the folder directly containing .obsidian as the root."],"tags":["obsidian","import","validation","sentinel-error","config"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}