siyuan-note/siyuan · error

Obsidian Vault config directory is missing

Error message

Obsidian Vault config directory is missing

What it means

`errObsidianVaultConfigMissing` is returned when the Vault root exists but its `.obsidian` configuration directory does not (`os.IsNotExist`), i.e. the folder is not actually an Obsidian Vault. It maps to i18n key 339. Other stat errors are wrapped as `errObsidianVaultUnreadable` instead.

Solutions

  1. Select the actual Vault root — the folder whose `.obsidian` subdirectory exists
  2. If `.obsidian` was removed by sync/backup exclusions, restore it (reopening the vault in Obsidian recreates it)
  3. Verify with a file browser that `<chosenPath>/.obsidian` exists before importing

Example fix

// before
validateObsidianVaultRoot("/home/user/plain-notes") // no .obsidian -> errObsidianVaultConfigMissing
// after
validateObsidianVaultRoot("/home/user/MyVault") // contains .obsidian/
Defensive patterns

Strategy: validation

Validate before calling

try {
  await fs.access(path.join(vaultPath, ".obsidian"));
} catch {
  throw new Error(`'${vaultPath}' has no .obsidian directory — not an Obsidian Vault`);
}

Type guard

function hasObsidianConfig(entries: string[]): boolean {
  return entries.includes(".obsidian");
}

Try / catch

try {
  await api.importObsidianVault(vaultPath);
} catch (e) {
  if (isVaultConfigMissing(e)) {
    // guide user to select the real vault root (folder containing .obsidian)
  }
}

Prevention

When it happens

Trigger: `validateObsidianVaultRoot` stats `<vault>/.obsidian` and gets a not-exist error — the chosen folder has no `.obsidian` subdirectory.

Common situations: User selected a plain folder of Markdown files never opened in Obsidian; the `.obsidian` folder was deleted/ignored by a sync tool (dotfolder excluded); user picked a parent folder above the actual vault.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/0aae26a4799f5bfa. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/import_obsidian.go:233

	return err.Cause.Error()
}

func (err *obsidianUserError) Unwrap() error {
	return err.Cause
}

func newObsidianUserError(detailLanguage int, relPath string, cause error) error {
	return &obsidianUserError{DetailLanguage: detailLanguage, RelPath: relPath, Cause: cause}
}

var (
	obsidianTasksMu                 sync.Mutex
	obsidianTasks                   = map[string]*obsidianTask{}
	obsidianActive                  string
	errObsidianVaultUnreadable      = errors.New("Obsidian Vault is unreadable")
	errObsidianVaultNotDirectory    = errors.New("Obsidian Vault path is not a directory")
	errObsidianVaultUnsafePath      = errors.New("Obsidian Vault path is unsafe")
	errObsidianVaultConfigMissing   = errors.New("Obsidian Vault config directory is missing")
	errObsidianVaultMarkdownMissing = errors.New("Obsidian Vault has no readable Markdown")
	errObsidianSourceChanged        = errors.New("Obsidian source file changed")

	obsidianBlockIDPattern  = regexp.MustCompile(`(?m)(?:^|[ \t])\^([A-Za-z0-9-]+)[ \t]*$`)
	obsidianQuotePattern    = regexp.MustCompile(`^((?:[ \t]*>[ \t]?)+)(.*)$`)
	obsidianListItemPattern = regexp.MustCompile(`^([ \t]*(?:[-+*]|\d+[.)])[ \t]+)(.*)$`)
	obsidianFootnotePattern = regexp.MustCompile(`(?m)\[\^[^\]\r\n]+\]`)
)

func StartObsidianVaultAnalysis(localPath string) (*ObsidianVaultTask, error) {
	var replacedTaskID string
	obsidianTasksMu.Lock()
	if obsidianActive != "" {
		if active := obsidianTasks[obsidianActive]; active != nil && !isObsidianTerminalState(active.State) {
			if !isObsidianPreImportState(active.State) {
				obsidianTasksMu.Unlock()
				return nil, errors.New(Conf.Language(329))
			}

View on GitHub (pinned to 9f775e8a12)