# How Claude-Obsidian Separates Vault Data from Plugin Installation

> Discover how Claude-Obsidian separates vault data from plugin installation with strict isolation and explicit rejection logic. Learn more about AgriciDaniel/claude-obsidian.

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

---

**Claude-Obsidian enforces strict isolation between user knowledge vaults and its own plugin installation through canonical path comparison and explicit rejection logic that prevents any vault operation from touching the plugin tree.**

The open-source tool **claude-obsidian** (available at `AgriciDaniel/claude-obsidian`) implements a rigorous architectural boundary between its executable code and user-generated content. Understanding **how claude-obsidian separates vault data from plugin installation** is critical for contributors and power users who need to guarantee that automated operations never corrupt the tool's own files or mistakenly treat the repository root as a knowledge vault.

## Vault Discovery with Explicit Plugin-Tree Exclusion

In [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py), the function `resolve_vault_root` orchestrates vault discovery while maintaining a hard boundary against the plugin installation directory.

The resolution follows a strict precedence chain:

1. An explicit `--vault` argument provided via CLI
2. The `CLAUDE_OBSIDIAN_VAULT` environment variable
3. The nearest workspace-config file ([`.claude-obsidian.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.claude-obsidian.json))
4. The nearest initialized vault directory

By default, the function sets `allow_plugin_root=False`, which automatically triggers the `assert_not_plugin_tree` validation before returning any path to the caller.

### The Plugin-Root Guard Clause

The helper `assert_not_plugin_tree` canonicalizes both the candidate vault path and the known plugin root, then compares their portable keys. If the candidate matches the plugin root exactly, the function raises `VaultSelectionError` with code `PLUGIN_ROOT_IS_NOT_VAULT`. If the candidate is a descendant of the plugin tree, it raises `PLUGIN_TREE_IS_NOT_VAULT` instead.

## Hard Enforcement via Canonical Path Validation

Path containment is not merely a convention in this codebase; it is enforced through filesystem-level canonicalization that prevents ambiguous boundary definitions.

### Portable Key Comparison

Rather than performing simple string comparisons, the system converts paths to portable keys (normalized, absolute representations) before evaluation. This prevents bypass attempts using symlinks or relative path traversal sequences that might otherwise confuse the boundary detection logic.

### Mutable Operation Protection

This validation layer guarantees that mutable operations—such as write locks, ledger updates, or file creation—are never performed inside the plugin's template or example hierarchies. The error is raised during the vault resolution phase, before any command execution or file system mutation begins.

## Vault Initialization Without Contamination

Once a vault root passes the separation checks, `build_vault_bundle` in [`claude_obsidian/vault_ops.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/vault_ops.py) generates the required directory structure.

This function receives a path that has already been validated by `resolve_vault_root`, ensuring that initialization creates `wiki/`, `.obsidian/`, `.raw/`, and the workspace config [`.claude-obsidian.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.claude-obsidian.json) only within the user vault. It cannot touch the plugin installation directory because the vault path is confirmed to be outside the plugin tree prior to the function's execution.

## Path Containment Helpers

Additional boundary protection is provided by utility functions in [`paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/paths.py) that runtime-verify all file operations:

- **`assert_within`**: Confirms that any target path remains inside the confirmed vault root before read or write operations execute.
- **`is_initialized_vault`**: Validates that a directory contains the markers of a legitimate vault without traversing into plugin directories.
- **`is_adoptable_vault`**: Determines if an existing directory can be adopted as a vault while maintaining the plugin-tree boundary.

Together, these utilities form a defense-in-depth strategy that contains all vault-specific operations within the user-designated zone.

## Implementation Example

The following patterns demonstrate the separation enforcement in practice:

```python
from claude_obsidian.paths import resolve_vault_root, VaultSelectionError
from pathlib import Path

# Attempt to resolve a vault from the current working directory

# The plugin_root parameter explicitly defines the forbidden zone

try:
    selection = resolve_vault_root(
        explicit=None,
        start=Path.cwd(),
        plugin_root=Path(__file__).parent.parent,  # Repository root

    )
    print(f"Vault found at: {selection.root}")
except VaultSelectionError as exc:
    print(f"Resolution failed: {exc.code} – {exc}")

```

```python
from claude_obsidian.vault_ops import build_vault_bundle

# Generate vault structure only after validation

# This operates exclusively on the previously validated vault path

bundle = build_vault_bundle(
    root=selection.root,
    operation_id="init-001",
    operation_type="init",
    generated_at="2024-01-01T00:00:00Z",
    adopt=False,
    force=False,
)
print(bundle["writes"])  # File list restricted to vault directory

```

## Summary

- **`resolve_vault_root`** in [`paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/paths.py) implements a precedence-based discovery algorithm that rejects the plugin tree by default via `allow_plugin_root=False`.
- **`assert_not_plugin_tree`** raises `VaultSelectionError` with specific codes (`PLUGIN_ROOT_IS_NOT_VAULT`, `PLUGIN_TREE_IS_NOT_VAULT`) when boundaries are violated.
- **`build_vault_bundle`** in [`vault_ops.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/vault_ops.py) operates only on pre-validated vault paths, ensuring initialization never contaminates the plugin installation.
- Path containment helpers (`assert_within`, `is_initialized_vault`, `is_adoptable_vault`) provide runtime verification that all operations stay within the vault boundary.
- The architecture guarantees that the plugin code remains isolated from user-generated knowledge vaults through canonical path comparison and early validation errors.

## Frequently Asked Questions

### Can I force claude-obsidian to use its own installation directory as a vault?

Yes, but only by explicitly setting `allow_plugin_root=True` when calling `resolve_vault_root`. The CLI does not expose this flag for safety reasons; it is reserved for internal testing scenarios. Without this override, the system raises `VaultSelectionError` with code `PLUGIN_ROOT_IS_NOT_VAULT` immediately upon detection.

### What happens if I try to initialize a vault inside the plugin templates folder?

The initialization will fail before creating any files. The `assert_not_plugin_tree` function canonicalizes the target path, detects that it resides within the plugin tree, and raises `VaultSelectionError` with code `PLUGIN_TREE_IS_NOT_VAULT`. This prevents accidental corruption of the tool's template hierarchy or example vaults distributed with the repository.

### How does the system handle symbolic links that point outside the vault?

The path containment helpers use canonical path resolution (portable keys) rather than string comparison. This means symlinks are resolved to their absolute targets before boundary checks occur, preventing escape attempts that could reach the plugin installation directory or other restricted system areas.

### Which source files contain the core separation logic?

The primary isolation logic resides in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py), specifically within the `resolve_vault_root` and `assert_not_plugin_tree` functions. The vault operation logic in [`claude_obsidian/vault_ops.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/vault_ops.py) relies on these validations but does not itself contain boundary enforcement code, ensuring single-responsibility separation between path validation and vault mutation.