How Claude-Obsidian Resolves Vault Selection: A Precedence-Based Algorithm

Claude-Obsidian resolves vault selection through a strict seven-step precedence hierarchy implemented in resolve_vault_root, checking explicit arguments, environment variables, workspace configs, and directory discovery before validating the final path.

The AgriciDaniel/claude-obsidian repository provides a CLI tool for managing Obsidian vaults with Claude AI integration. When multiple vault candidates exist on a system, the tool must deterministically select a single root directory to prevent accidental operations on the wrong workspace. Understanding this Claude-Obsidian vault selection logic is essential for troubleshooting configuration issues and optimizing your workflow.

The Vault Resolution Hierarchy

The resolve_vault_root function in claude_obsidian/paths.py implements a cascading decision tree that aborts immediately if it cannot identify a single safe vault. The algorithm evaluates candidates in the following strict order:

Explicit Path Arguments

The highest priority source is the explicit parameter passed directly to the function. When you provide a --vault path via CLI or call resolve_vault_root(explicit="~/my-vault"), the code canonicalizes that path and uses it without further searching. The source is recorded as "explicit".

This override ensures that user-specified paths always take precedence over automatic discovery mechanisms.

Environment Variable Configuration

If no explicit path is provided, the function checks for the CLAUDE_OBSIDIAN_VAULT environment variable. When present, the value is canonicalized and selected immediately, with the source marked as "environment".

This method is ideal for CI/CD pipelines or users who maintain a primary vault in a non-standard location.

Workspace Configuration Discovery

Absent an explicit argument or environment variable, the algorithm traverses upward from the current working directory searching for a .claude-obsidian.json configuration file. The internal helper _find_workspace_config handles this directory walk.

If found, _read_workspace_config parses the JSON and extracts the vault field to determine the candidate path. This source is labeled "workspace-config".

Automatic Discovery via Directory Traversal

When no workspace configuration exists, the code executes _nearest_vault to locate the closest initialized vault by walking up the directory tree from the current working directory. An initialized vault must contain:

  • A wiki/ directory
  • Either a .obsidian/ or .raw/ directory

This discovery method is tagged as "cwd-discovery" and serves as the default fallback for users running commands from within their vault subdirectories.

Safety Mechanisms and Validation

Once a candidate path is identified, Claude-Obsidian applies several safeguards before finalizing the selection.

Plugin-Root Protection

The assert_not_plugin_tree function prevents the algorithm from selecting the plugin's own installation directory as a vault. If the resolved path would fall within the plugin repository tree, the system raises a VaultSelectionError with the code PLUGIN_ROOT_IS_NOT_VAULT.

You can bypass this protection only by explicitly passing allow_plugin_root=True to resolve_vault_root, though this is strongly discouraged in production environments.

Path Validation and Initialization Checks

The validate_vault_root function performs mandatory verification on the candidate path:

  • Verifies the path exists and is a directory
  • Checks for required sentinel files (unless allow_uninitialized=True)
  • Raises VaultSelectionError with codes VAULT_NOT_FOUND or VAULT_MISSING if validation fails

These checks ensure that read/write operations target legitimate Obsidian vault structures rather than arbitrary directories.

Legacy Vault Detection

Finally, the algorithm determines whether the selected vault is legacy by checking for the absence of a .claude-obsidian.json configuration file. The function returns a VaultSelection dataclass containing:

VaultSelection(
    root=Path(<resolved-vault>),
    source="<explicit|environment|workspace-config|cwd-discovery>",
    legacy=True|False,
)

Implementation Details in paths.py

The core logic resides in claude_obsidian/paths.py, which exports the primary resolve_vault_root function. This module maintains separation between resolution logic (resolve_vault_root), filesystem crawling (_find_workspace_config, _nearest_vault), and validation (validate_vault_root, assert_not_plugin_tree).

The vault_ops.py module consumes these resolved paths to execute initialization and adoption operations, while tests/test_vault_root_separation.py and tests/test_vault_ops.py verify the precedence rules and error handling across different invocation contexts.

Usage Examples

Explicit Vault Path

from claude_obsidian.paths import resolve_vault_root

selection = resolve_vault_root(explicit="~/my-vault")
print(selection.root)   # → /home/user/my-vault

print(selection.source) # → explicit

Environment Variable Configuration

Set the variable in your shell:

export CLAUDE_OBSIDIAN_VAULT=/home/user/obsidian-vault

Then resolve without arguments:

selection = resolve_vault_root()
print(selection.root)   # → /home/user/obsidian-vault

print(selection.source) # → environment

Workspace Config Discovery

With a .claude-obsidian.json containing {"vault": "my-vault"} in the current directory:

selection = resolve_vault_root()
print(selection.root)   # → /current/dir/my-vault

print(selection.source) # → workspace-config

Nearest Initialized Vault

When running from a subdirectory of an initialized vault:


# Directory layout:

# /projects/obsidian/          ← initialized vault (contains wiki/)

# /projects/obsidian/sub/      ← cwd

selection = resolve_vault_root(start="/projects/obsidian/sub")
print(selection.root)   # → /projects/obsidian

print(selection.source) # → cwd-discovery

Preventing Plugin Tree Selection


# If the plugin installation lives at /opt/claude-obsidian

selection = resolve_vault_root(start="/opt/claude-obsidian")

# → VaultSelectionError: PLUGIN_ROOT_IS_NOT_VAULT

Summary

  • Claude-Obsidian vault selection follows a strict precedence: explicit arguments override environment variables, which override workspace configs, which override automatic directory discovery.
  • The resolve_vault_root function in claude_obsidian/paths.py implements this hierarchy and returns a VaultSelection dataclass containing the resolved path, discovery source, and legacy status.
  • Safety mechanisms include plugin-root protection via assert_not_plugin_tree and mandatory path validation through validate_vault_root.
  • Unresolvable selections raise specific VaultSelectionError codes including VAULT_NOT_FOUND, VAULT_MISSING, and PLUGIN_ROOT_IS_NOT_VAULT.

Frequently Asked Questions

How does Claude-Obsidian choose between multiple vaults in the same parent directory?

Claude-Obsidian selects the nearest initialized vault by walking up the directory tree from the current working directory. It stops at the first vault containing both a wiki/ directory and either .obsidian/ or .raw/ subdirectories. If multiple vaults exist at the same level, the algorithm does not arbitrarily choose between them; instead, it selects based on proximity to your current location in the filesystem hierarchy.

Can I override automatic vault discovery without modifying environment variables?

Yes. Pass the explicit parameter directly to resolve_vault_root or use the --vault CLI argument. This explicit path takes precedence over all other discovery methods, including the CLAUDE_OBSIDIAN_VAULT environment variable and .claude-obsidian.json workspace configurations.

What happens if my vault lacks the required Obsidian directories?

If the candidate path fails validation—meaning it lacks the required wiki/ directory and either .obsidian/ or .raw/ subdirectories—the validate_vault_root function raises a VaultSelectionError with code VAULT_MISSING. You can bypass this check by passing allow_uninitialized=True to resolve_vault_root, though this is only recommended when initializing a new vault structure.

Why does Claude-Obsidian refuse to use its own installation directory as a vault?

The assert_not_plugin_tree function raises VaultSelectionError: PLUGIN_ROOT_IS_NOT_VAULT to prevent accidental data corruption. Since the plugin manages its own Python source code and dependencies internally, treating this location as a user vault could lead to catastrophic data loss. This protection can only be disabled by passing allow_plugin_root=True, which is reserved for development and testing scenarios.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →