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

> Discover how Claude-Obsidian resolves vault selection using a seven-step precedence algorithm. Learn how it prioritizes explicit args, env vars, workspace configs, and directory discovery.

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

---

**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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.claude-obsidian.json) configuration file. The function returns a `VaultSelection` dataclass containing:

```python
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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/vault_ops.py) module consumes these resolved paths to execute initialization and adoption operations, while [`tests/test_vault_root_separation.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/tests/test_vault_root_separation.py) and [`tests/test_vault_ops.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/tests/test_vault_ops.py) verify the precedence rules and error handling across different invocation contexts.

## Usage Examples

### Explicit Vault Path

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

```bash
export CLAUDE_OBSIDIAN_VAULT=/home/user/obsidian-vault

```

Then resolve without arguments:

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

print(selection.source) # → environment

```

### Workspace Config Discovery

With a [`.claude-obsidian.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.claude-obsidian.json) containing `{"vault": "my-vault"}` in the current directory:

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

```python

# 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

```python

# 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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/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`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.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.