How Claude‑Obsidian Resolves Vault Locations: Precedence Rules and Safety Mechanisms

Claude‑obsidian resolves vault locations through a deterministic seven‑step algorithm implemented in claude_obsidian/paths.py that prioritizes explicit arguments, environment variables, workspace configuration files, and nearest‑parent discovery, while enforcing strict validation to prevent accidental writes inside the plugin installation tree.

Claude‑obsidian, an open‑source bridge between Claude AI and Obsidian vaults maintained in the AgriciDaniel/claude‑obsidian repository, requires a robust method to locate user vaults across diverse environments. The resolution logic is centralized in the resolve_vault_root function, which implements a precedence‑driven selection process with multiple fallback layers and safety guards.

Resolution Precedence Hierarchy

The resolve_vault_root function evaluates potential vault locations in strict order, aborting with a VaultSelectionError if validation fails at any stage.

Explicit Path Argument

The highest priority source is the explicit parameter passed directly to resolve_vault_root. When provided, the function canonicalizes the path using Path(...).resolve() and immediately proceeds to validation. The selection source is recorded as "explicit".


# From claude_obsidian/paths.py

if explicit is not None:
    candidate = canonical(explicit)
    source = "explicit"

CLAUDE_OBSIDIAN_VAULT Environment Variable

If no explicit argument is provided, the function checks for the CLAUDE_OBSIDIAN_VAULT environment variable. When present, its value becomes the candidate path with the source labeled "environment".

elif env.get("CLAUDE_OBSIDIAN_VAULT"):
    candidate = canonical(env["CLAUDE_OBSIDIAN_VAULT"])
    source = "environment"

Workspace Configuration File

When the environment variable is absent, the algorithm searches upward from the current working directory for a .claude‑obsidian.json file. The helper _find_workspace_config reads this JSON, validates it against VAULT_SCHEMA, and extracts the "vault" field to determine the absolute path. This selection is tagged with source "workspace‑config".

configured = _find_workspace_config(cwd)
if configured:
    candidate = configured
    source = "workspace-config"

Nearest Initialized Vault Discovery

If no configuration file exists, the system falls back to directory traversal. The _nearest_vault helper walks upward from the current working directory looking for an initialized vault—defined as a directory containing a wiki/ subdirectory and either a .obsidian/ or .raw/ directory. This discovery method uses source "cwd‑discovery".

nearest = _nearest_vault(cwd)
if nearest:
    candidate = nearest
    source = "cwd-discovery"

Safety Guards and Validation

Before returning a vault location, claude‑obsidian enforces multiple safety checks to prevent data corruption and ensure vault integrity.

Plugin Tree Protection

Implicit discovery (workspace config and nearest vault steps) is blocked from selecting any path inside the claude‑obsidian installation directory. The assert_not_plugin_tree helper raises an error unless allow_plugin_root=True is explicitly passed, preventing accidental writes into the repository itself.

assert_not_plugin_tree(cwd, plugin, source="cwd-discovery")

Vault Structure Validation

The validate_vault_root function confirms that the candidate:

  • Exists as a filesystem entity
  • Is a directory (not a file)
  • Contains required sentinel files (wiki/ plus .obsidian/ or .raw/) unless allow_uninitialized=True

Failures raise VaultSelectionError with specific error codes: VAULT_MISSING, VAULT_NOT_DIRECTORY, or VAULT_SENTINEL_MISSING.

validate_vault_root(candidate, allow_uninitialized=allow_uninitialized)

Return Value and Cross‑Platform Handling

Upon successful validation, resolve_vault_root returns a VaultSelection dataclass containing:

  • root: The resolved Path object (canonicalized and absolute)
  • source: A string indicating the selection method ("explicit", "environment", "workspace-config", or "cwd-discovery")
  • legacy: A boolean flag (currently always False)

The implementation uses Path.resolve() for cross‑platform compatibility, with additional safeguards like is_name_surrogate and assert_unaliased_directory to handle edge cases on POSIX and Windows systems.

Practical Code Examples

from claude_obsidian.paths import resolve_vault_root
import os

# Example 1: Explicit path (highest priority)

selection = resolve_vault_root("/path/to/my/vault")
print(selection.root)   # /path/to/my/vault

print(selection.source) # explicit

# Example 2: Environment variable fallback

os.environ["CLAUDE_OBSIDIAN_VAULT"] = "/env/vault"
selection = resolve_vault_root()
print(selection.source) # environment

# Example 3: Workspace config discovery

# Assumes .claude-obsidian.json exists in cwd or parent

selection = resolve_vault_root()
print(selection.source) # workspace-config

# Example 4: Implicit nearest vault discovery

# Finds nearest parent with wiki/ and .obsidian/

selection = resolve_vault_root()
print(selection.source) # cwd-discovery

Summary

  • Deterministic precedence: Explicit argument → Environment variable → Workspace config → Nearest initialized vault.
  • Safety first: Plugin tree protection prevents accidental writes into the installation directory.
  • Strict validation: All candidates must pass existence, directory, and sentinel checks unless explicitly overridden.
  • Cross‑platform: Uses pathlib.Path with resolution guards for consistent behavior on Windows and POSIX.
  • Clear provenance: The VaultSelection object records exactly how the vault was discovered for debugging and auditing.

Frequently Asked Questions

What happens if multiple vault location sources are available?

Claude‑obsidian uses strict precedence: explicit arguments always win, followed by the CLAUDE_OBSIDIAN_VAULT environment variable, then workspace configuration files, and finally automatic directory discovery. The first valid source in this hierarchy is selected; lower‑priority sources are ignored even if present.

Can claude‑obsidian use a vault inside its own installation directory?

By default, no. The assert_not_plugin_tree check raises a VaultSelectionError if implicit discovery selects a path within the plugin root. You must pass allow_plugin_root=True to override this protection, which is strongly discouraged to prevent corruption of the installation.

What files must exist for a directory to qualify as an initialized vault?

According to the validation logic in validate_vault_root, an initialized vault must contain a wiki/ subdirectory and either a .obsidian/ or .raw/ directory. You can bypass the sentinel check by setting allow_uninitialized=True, though this is not recommended for standard operations.

How does claude‑obsidian handle relative paths in configuration files?

The _find_workspace_config helper resolves relative paths found in .claude‑obsidian.json against the configuration file's directory, converting them to absolute paths before validation. All final paths are canonicalized using Path.resolve() to eliminate symlinks and relative components.

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 →