# Which Paths Are Protected from User-Authored Bundles in Claude-Obsidian

> Discover which paths are protected from user-authored bundles in Claude-Obsidian. Learn how the assert_not_plugin_tree function secures your plugin installation from unauthorized writes.

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

---

**The entire plugin installation tree—including the root directory and all descendants—is protected from user-authored bundles through the `assert_not_plugin_tree` function in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py), which rejects any write operations with specific error codes.**

Claude-Obsidian is a Python-based automation layer for Obsidian vaults that enforces strict filesystem boundaries to prevent corruption of its own codebase. The repository treats its installation directory as immutable, ensuring that user-created vaults, captures, or transactions cannot accidentally overwrite core program files. This protection mechanism relies on canonical path comparison using portable keys to identify and block attempts to write inside the plugin tree.

## Protected Directory Hierarchy

The path protection system covers two specific categories of locations within the Claude-Obsidian installation.

### The Plugin Root Directory

The **plugin root** itself—the directory containing the installed Claude-Obsidian code—is strictly protected against mutable operations. When `assert_not_plugin_tree` canonicalizes a candidate path and finds that its portable key exactly matches the plugin root's portable key, the function raises `VaultSelectionError` with the code **`PLUGIN_ROOT_IS_NOT_VAULT`**. This prevents users from designating the installation directory itself as a vault location.

### Descendant Directories

All **subdirectories** of the plugin root are equally protected, including paths such as `scripts/`, `assets/`, `claude_obsidian/`, `tests/`, and `.git/`. The enforcement logic checks whether the candidate path's portable key begins with the plugin root key followed by a forward slash (`/`). If this prefix match succeeds, the function raises `VaultSelectionError` with the code **`PLUGIN_TREE_IS_NOT_VAULT`**, identifying the candidate as a descendant of the product tree.

## How the Enforcement Works

The core validation logic resides in **[`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py)**, specifically within the `assert_not_plugin_tree` helper function. This function performs **portable key comparison** rather than simple string matching, ensuring cross-platform reliability when detecting path relationships on different operating systems.

When resolving a vault root—typically through the **`resolve_vault_root`** function—the code invokes `assert_not_plugin_tree` to validate the resolved path. If the path represents either the plugin root itself or any nested directory within it, the operation aborts immediately before any filesystem mutation occurs. The transaction layer in **[`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py)** indirectly benefits from these protections by utilizing the same vault-resolution helpers.

## Code Example

The following example demonstrates the protection mechanism when attempting to resolve a vault inside the installation tree:

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

try:
    # Attempt to use a directory that lies inside the installed product tree

    selection = resolve_vault_root(explicit="./claude_obsidian")
except VaultSelectionError as exc:
    print(f"Blocked path: {exc.code} – {exc}")

# Output:

# Blocked path: PLUGIN_ROOT_IS_NOT_VAULT – refusing to write mutable vault state into the plugin installation

```

If the candidate path were a deeper subdirectory (for example, `./claude_obsidian/scripts`), the same exception type would raise with the code `PLUGIN_TREE_IS_NOT_VAULT` instead.

## Key Files Involved

The path protection system spans several critical files within the repository:

- **[`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py)**: Implements `assert_not_plugin_tree` and integrates the protection into the `resolve_vault_root` resolution pipeline.
- **[`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py)**: Consumes the vault-resolution helpers, inheriting the protected-path behavior for all transaction operations.
- **[`tests/test_paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/tests/test_paths.py)**: Contains verification tests confirming that attempts to resolve vaults inside the plugin tree correctly raise `VaultSelectionError`.

## Summary

- **The entire plugin installation tree** is treated as read-only, including both the root directory and all descendants.
- **`assert_not_plugin_tree`** in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py) performs canonical path comparison using portable keys to enforce these boundaries.
- Two distinct error codes identify violations: **`PLUGIN_ROOT_IS_NOT_VAULT`** for the root itself, and **`PLUGIN_TREE_IS_NOT_VAULT`** for subdirectories.
- The protection activates during **vault resolution**, ensuring user-authored bundles cannot alter Claude-Obsidian's codebase.

## Frequently Asked Questions

### What happens if I try to create a vault inside the Claude-Obsidian installation directory?

The operation fails with a `VaultSelectionError`. If you target the plugin root directly, you receive the `PLUGIN_ROOT_IS_NOT_VAULT` error code; if you target a subdirectory like `scripts/` or `assets/`, you receive `PLUGIN_TREE_IS_NOT_VAULT`.

### How does Claude-Obsidian distinguish between the plugin root and its subdirectories?

The `assert_not_plugin_tree` function compares **portable keys** derived from canonicalized paths. An exact match triggers the root-specific error, while a prefix match followed by a forward slash indicates a descendant directory, triggering the tree-specific error.

### Which functions trigger the protected path check?

The **`resolve_vault_root`** function in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py) invokes the protection logic automatically. Consequently, any operation relying on vault resolution—including transaction processing in [`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py)—inherits these safeguards.

### Can I disable the protected path restrictions?

No. According to the source code in [`claude_obsidian/paths.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/paths.py), these protections are hardcoded into the vault resolution pipeline to guarantee codebase integrity. There is no configuration option to override `assert_not_plugin_tree` or bypass the portable key validation.