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

> Discover how claude-obsidian resolves vault locations using a 7-step algorithm that prioritizes explicit arguments, environment variables, and workspace config for safe, deterministic discovery.

- Repository: [Agrici.Daniel/claude-obsidian](https://github.com/AgriciDaniel/claude-obsidian)
- Tags: how-to-guide
- Published: 2026-08-28

---

**Claude‑obsidian resolves vault locations through a deterministic seven‑step algorithm implemented in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/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"`.

```python

# 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"`.

```python
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"`.

```python
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"`.

```python
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.

```python
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`.

```python
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

```python
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.