Where Are Captured Content Files Stored in Claude-Obsidian?

Captured content files are stored in the immutable .raw/captured subdirectory of the vault root, with the path enforced by transaction validation logic in transaction.py and constructed via the _raw_captured_path() helper in capture.py.

The claude-obsidian repository implements a content-addressable storage system that captures files from an inbox and moves them into a protected raw payload area. Understanding exactly where captured content files are stored in claude-obsidian is essential for vault maintenance, backup strategies, and debugging capture workflows.

The .raw/captured Directory Structure

Captured files reside under the vault root in a dedicated immutable directory tree. The storage hierarchy follows this pattern:


<vault-root>/
 └─ .raw/
     └─ captured/
         ├─ a3f5c9e1b2.bin
         ├─ 7d4e2f9a1c.md
         └─ …

This location serves as the raw payload directory for all captured content. Once written, files in this tree become read-only for normal operations, ensuring content immutability after capture. The system uses content-addressable storage, meaning the filename typically derives from a hash digest of the file contents, often with an appropriate suffix to preserve the original extension.

How the Capture Path Is Constructed

The claude_obsidian/capture.py module builds the destination path through the internal helper _raw_captured_path(). According to the source code at lines 872–889, this function resolves the final storage location using:

vault / ".raw" / "captured" / f"{digest}{suffix}"

The capture_filesystem() function leverages this helper to move files from the inbox into the vault. It calculates the content hash, constructs the relative path, and performs the atomic move operation while preserving metadata about the original location.

Transaction Validation Enforcement

The system strictly confines capture operations to this specific directory through validation logic in claude_obsidian/transaction.py. At line 3239, the code explicitly checks path components:

or parts[:2] != (".raw", "captured")

This validation rejects any capture operation that does not target a path whose first two components are exactly ".raw" and "captured". This security boundary prevents capture primitives from writing to arbitrary locations within the vault, ensuring captured content remains isolated in the designated immutable zone.

Practical Example

Below is a runnable example demonstrating how to capture a file and verify its storage location:

from pathlib import Path
from claude_obsidian.capture import capture_filesystem

# Initialize vault and source paths

vault = Path("/path/to/my-vault")
source = vault / "inbox" / "notes.md"

# Execute capture operation

result = capture_filesystem(vault, source)

# Verify storage location

print("Captured file stored at:", result["stored_path"])

# Output: .raw/captured/7d4e2f9a1c.md

The function returns a metadata dictionary containing the stored_path key, which holds the relative path (e.g., ".raw/captured/7d4e2f9a1c.md"). This path is always relative to the vault root and strictly adheres to the .raw/captured prefix enforced by the transaction layer.

Summary

  • Primary storage location: All captured files live under <vault-root>/.raw/captured/.
  • Path construction: The _raw_captured_path() helper in capture.py (lines 872–889) builds content-addressed filenames using hash digests.
  • Validation: transaction.py line 3239 enforces the (".raw", "captured") path prefix, rejecting unauthorized destination paths.
  • Immutability: The .raw tree is read-only for normal operations, guaranteeing captured files cannot be modified after storage.
  • API response: The capture_filesystem() function returns the relative storage path in the stored_path field of its result dictionary.

Frequently Asked Questions

Can captured files be moved after storage?

No, the .raw/captured directory is designed as an immutable storage area. According to the implementation in claude_obsidian/capture.py, once a file moves into the .raw tree, normal operations treat this path as read-only. Moving or modifying captured files would violate the content-addressable integrity guarantees and trigger validation errors in the transaction layer.

What happens if the .raw/captured directory does not exist?

The capture_filesystem() function and its helper _raw_captured_path() handle directory creation automatically. When initiating a capture operation, the system ensures the full path vault/.raw/captured/ exists before attempting to move the file, creating parent directories as needed with appropriate permissions.

How does claude-obsidian prevent captures to arbitrary vault locations?

The transaction validation logic in claude_obsidian/transaction.py explicitly inspects the path components of any capture operation. At line 3239, the code validates that parts[:2] != (".raw", "captured") returns False, meaning any path not starting with these exact components gets rejected. This sandboxing ensures capture primitives cannot escape the designated raw payload directory.

Are file extensions preserved in the captured storage path?

Yes, the _raw_captured_path() implementation preserves the original file extension by appending it as a suffix to the content hash. For example, a file named document.md with hash 7d4e2f9a1c becomes 7d4e2f9a1c.md within the .raw/captured directory, maintaining type identification while ensuring content-addressable uniqueness.

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 →