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

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, 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, 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 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:

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: Implements assert_not_plugin_tree and integrates the protection into the resolve_vault_root resolution pipeline.
  • claude_obsidian/transaction.py: Consumes the vault-resolution helpers, inheriting the protected-path behavior for all transaction operations.
  • 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 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 invokes the protection logic automatically. Consequently, any operation relying on vault resolution—including transaction processing in 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, 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.

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 →