# Reserved Paths in Claude-Obsidian: What User-Authored Bundles Cannot Overwrite

> Discover reserved paths in Claude-Obsidian that user-authored bundles cannot overwrite. Learn how this feature enforces data integrity and protects your setup.

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

---

**Claude-Obsidian enforces data integrity by maintaining a hard-coded list of reserved paths in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py) that user-authored bundles are strictly forbidden from modifying or replacing.**

Claude-Obsidian is an agent-skill framework that constructs Obsidian-flavoured knowledge bases from Claude model responses. To protect core metadata and ensure deterministic replay of knowledge updates, the repository implements an immutability layer that prevents writes to critical system files. Understanding these reserved paths is essential for developers extending the tool or creating custom vault integrations.

## What Are Reserved Paths in Claude-Obsidian?

Reserved paths are filesystem locations within a Claude-Obsidian vault that the system protects from modification by user-authored bundles. These paths contain essential metadata, provenance ledgers, seed templates, and helper scripts that maintain the structural integrity of the knowledge base. According to the source code in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py), any attempt to write to these locations triggers a validation error and aborts the transaction.

The validation logic lives in the `RESERVED_PATHS` constant and the accompanying `is_reserved_path()` function. When a bundle is submitted, the `validate_bundle()` function checks the target path against this list, ensuring that immutable components remain untouched during normal operations.

## The Complete List of Reserved Paths

The `RESERVED_PATHS` list in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py) defines five specific locations that bundles cannot overwrite:

- **[`.raw/.manifest.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.raw/.manifest.json)** – The global manifest that records immutable source payloads and tracks the provenance of ingested data.
- **[`.vault-meta/keep-alive.sh`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.vault-meta/keep-alive.sh)** – A helper script designed for long-running processes that must remain unchanged to ensure vault stability.
- **`wiki/meta/ledgers/`** – The directory containing signed provenance ledgers for knowledge claims and audit trails.
- **`wiki/.gitkeep`** – A placeholder file that preserves the `wiki/` directory structure in version control systems.
- **`templates/`** – The entire template tree containing seed vault content; this directory and all sub-paths are protected as immutable seed data.

The system performs path normalization using `pathlib.Path.as_posix()` to ensure cross-platform compatibility when checking these reserved locations.

## How Bundle Validation Works

The validation pipeline relies on two core functions defined in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py):

**`is_reserved_path(path: str) -> bool`** – This function normalizes the input path to POSIX style and checks it against the `RESERVED_PATHS` list. It returns `True` if the path matches exactly or if it resides within a reserved directory tree (detected via `startswith` logic).

**`validate_bundle(bundle: Dict[str, Any]) -> None`** – This function extracts the `"path"` field from a bundle dictionary and invokes `is_reserved_path()`. If the path is reserved, the function raises a `ValueError` with the message `"Attempt to write to reserved path: {path}"`, preventing the transaction from committing.

```python
from claude_obsidian.helpers import validate_bundle, is_reserved_path

# Check if a path is reserved

print(is_reserved_path(".raw/.manifest.json"))  # True

print(is_reserved_path("wiki/notes/Paris.md"))  # False

```

## Practical Examples: Validating Bundle Writes

When building custom integrations, you must ensure your bundles target writable locations. The following examples demonstrate both valid and invalid operations:

**Valid Bundle: Writing to the Wiki**

```python
from claude_obsidian.helpers import validate_bundle

# This succeeds – the path is not reserved

valid_bundle = {
    "path": "wiki/Paris.md",
    "content": "# Paris\n\nThe capital of France."

}

validate_bundle(valid_bundle)  # No exception raised

```

**Invalid Bundle: Attempting to Overwrite the Manifest**

```python
from claude_obsidian.helpers import validate_bundle

# This fails – .raw/.manifest.json is reserved

invalid_bundle = {
    "path": ".raw/.manifest.json",
    "content": "{}"
}

try:
    validate_bundle(invalid_bundle)
except ValueError as e:
    print(e)  # Output: Attempt to write to reserved path: .raw/.manifest.json

```

**Invalid Bundle: Writing Under the Templates Tree**

```python

# This also fails – the entire templates/ directory is protected

template_bundle = {
    "path": "templates/custom/page.md",
    "content": "# Custom Template"

}

validate_bundle(template_bundle)  # Raises ValueError

```

## Summary

- The **reserved paths in Claude-Obsidian** are hard-coded in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py) within the `RESERVED_PATHS` constant.
- Protected locations include the manifest file ([`.raw/.manifest.json`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.raw/.manifest.json)), vault metadata scripts ([`.vault-meta/keep-alive.sh`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.vault-meta/keep-alive.sh)), provenance ledgers (`wiki/meta/ledgers/`), directory placeholders (`wiki/.gitkeep`), and the entire seed template tree (`templates/`).
- The `is_reserved_path()` function checks for exact matches and sub-directory relationships using POSIX-style path normalization.
- The `validate_bundle()` function enforces these restrictions by raising a `ValueError` when any bundle attempts to write to a reserved location.
- These protections ensure immutable source payloads remain intact and the vault structure maintains deterministic behavior across transactions.

## Frequently Asked Questions

### What happens if a bundle tries to write to a reserved path?

According to the implementation in [`claude_obsidian/helpers.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/helpers.py), the `validate_bundle()` function raises a `ValueError` with the message `"Attempt to write to reserved path: {path}"`. This error aborts the current transaction, preventing any modifications to the protected file or directory.

### Can I modify the keep-alive.sh script in my vault?

No, the [`.vault-meta/keep-alive.sh`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/.vault-meta/keep-alive.sh) script is explicitly listed in `RESERVED_PATHS` to ensure long-running processes remain stable. If you need custom process management, you should create new scripts in unreserved locations such as `wiki/scripts/` or `.vault-meta/custom/` rather than modifying the reserved default.

### Why is the templates directory protected?

The `templates/` directory contains the seed data used to initialize fresh vaults during the `claude_obsidian init` command. Protecting this entire tree ensures that every new vault starts with a consistent, known-good structure. User-authored bundles should write content to `wiki/` or other user-space directories rather than attempting to replace the core template files.

### How do I add custom metadata if the meta directories are reserved?

While `wiki/meta/ledgers/` is reserved for system provenance tracking, you can create custom metadata structures in unreserved paths such as `wiki/meta/custom/` or `wiki/.meta/`. The reserved status applies only to the specific paths listed in `RESERVED_PATHS`, not to the entire `wiki/` hierarchy.