# How claude-obsidian Ensures Transactional Integrity for Vault Mutations

> Discover how claude-obsidian ensures transactional integrity for vault mutations. Learn about its atomic, recoverable protocol combining locking, pre-validation, and rollback.

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

---

**claude-obsidian guarantees atomic, recoverable vault mutations through a layered transaction protocol that combines process-level locking, cryptographic pre-validation, and deterministic rollback mechanisms.**

The open-source tool `AgriciDaniel/claude-obsidian` implements a robust transactional system for Obsidian vault operations, ensuring that every modification follows an all-or-nothing commit model. By treating each vault change as a recoverable, operation-level transaction, the system prevents partial writes, detects external interference, and maintains data consistency even during system failures.

## Core Mechanisms for Transactional Integrity

### Process-Level Mutation Locking

Before any write operation begins, the system establishes an exclusive **mutation lock** using directory-descriptor confinement. In [`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py), the functions `_require_lock_dirfd_support()` and `_open_lock_root_fd()` (lines 66-78) open a locked directory descriptor that cannot be swapped by other processes during the transaction lifecycle.

This lock is held for the entire transaction duration, preventing concurrent interference from external tools or parallel processes. The directory descriptor acts as a single-process gatekeeper, ensuring that no other mutation can begin until the current transaction either commits fully or rolls back completely.

### Pre-Condition Hash Validation

Every transaction begins by capturing the **current cryptographic state** of target files. The system reads each file planned for modification and records its SHA-256 digest and file mode using `_safe_file_state` (lines 44-52). These hashes serve as pre-conditions that must remain unchanged throughout the transaction.

If any monitored file changes between the initial read and the final write, the system raises a `TransactionConflict` exception and immediately aborts. This optimistic concurrency control prevents the "lost update" problem and ensures that external modifications do not corrupt the planned operation sequence.

### Atomic File Replacement Strategy

Individual file writes leverage atomic replacement to eliminate partial state exposure. The `_atomic_vault_write` function (lines 37-45) creates temporary files with the `.txn-…` prefix, opening them with `O_EXCL|O_NOFOLLOW` flags to prevent symlink attacks and accidental overwrites.

Once the temporary file contains the complete new content, the system calls `os.replace()` to perform an atomic swap into the target location. This mechanism guarantees that readers always see either the previous valid state or the new complete state—never a partially-written file, even if the process crashes mid-write.

### Deterministic Rollback and Recovery

For every write operation, the system stores the original hash and mode in a **transaction journal** located at `.vault-meta/transactions`. If any step fails during commit, the journal enables deterministic replay via `_confined_vault_unlink` and related helpers (lines 90-100).

The recovery process verifies each original file against its stored SHA-256 hash before restoration, ensuring that rollback operations themselves do not introduce corruption. This creates a safe unwind path that returns the vault to its exact pre-transaction state, regardless of how many files were modified before the failure occurred.

## Safety and Validation Guarantees

### Path Validation and Reserved Directory Protection

The system enforces strict path hygiene to prevent aliasing attacks and metadata corruption. In [`claude_obsidian/transaction.py`](https://github.com/AgriciDaniel/claude-obsidian/blob/main/claude_obsidian/transaction.py) (lines 60-78 and 88-100), the code defines `_RESERVED_WRITE_PATHS` that forbid writes to internal metadata directories. Every target path undergoes portability validation to ensure:

- No Windows-illegal characters exist in filenames
- Canonical Unicode NFC normalization
- Length limits compliance for cross-platform compatibility

These checks eliminate naming attacks that could otherwise bypass security boundaries or corrupt the vault's internal structure.

### Cryptographic Plan Approval

Each transaction bundle receives a cryptographic fingerprint before execution. The `plan_approval_sha256` function (lines 124-132) computes a SHA-256 hash binding the operation type, vault identity, and exact set of planned writes. This hash is stored in the transaction journal and verified before execution.

By requiring plan approval, the system ensures that the operation set cannot be tampered with between user consent and actual execution. The approval hash acts as a tamper-evident seal over the entire transaction plan.

### Cross-Platform Safety Checks

On platforms lacking directory-descriptor support (such as native Windows without WSL), the system explicitly rejects mutation attempts. The `_require_write_platform` function (lines 108-116) detects capabilities and falls back to read-only inspection mode when directory locking is unavailable.

This platform-aware behavior maintains the same safety guarantees across operating systems by preventing execution on hosts that cannot support the required atomicity primitives.

## Implementation Example: Executing a Safe Transaction

The following Python pattern demonstrates how to prepare and execute a transaction with full integrity checks:

```python
from pathlib import Path
from claude_obsidian.transaction import (
    plan_approval_sha256,
    _atomic_vault_write,
    safe_transactions_root,
)

vault = Path("/path/to/vault")
bundle = {"operation": "save", "content": "Hello world"}
writes = [
    # PreparedWrite contains file metadata and content hashes

    type("PreparedWrite", (), {
        "relative_path": "wiki/page.md",
        "mode": 0o644,
        "original_sha256": "a3f5...",
        "original_mode": 0o644,
        "content_sha256": "e3b0c442...",
        "new_mode": 0o644,
        "data": b"Hello world\n",
    })
]

# Generate approval hash for tamper detection

approval_hash = plan_approval_sha256(vault, bundle, writes)

# Apply writes atomically

for w in writes:
    _atomic_vault_write(vault, w.relative_path, w.data, mode=w.new_mode)

# Record to journal for potential rollback

journal_dir = safe_transactions_root(vault, create=True)

# Journal entry contains approval_hash and write metadata...

```

## Recovery and Error Handling

When transactions fail, the journal enables automatic recovery:

```python
from claude_obsidian.transaction import TransactionRecoveryError, _confined_vault_unlink

try:
    # ... execute writes ...

    pass
except Exception:
    for w in writes:
        try:
            _confined_vault_unlink(
                vault, 
                w.relative_path, 
                expected_sha256=w.original_sha256
            )
        except TransactionRecoveryError:
            # Log and continue; system maintains eventual consistency

            pass
    raise

```

## Summary

- **Process-level locking** via directory descriptors prevents concurrent mutations during transaction execution.
- **SHA-256 pre-condition hashes** detect external file modifications before they corrupt the transaction.
- **Atomic replacement** using `os.replace()` ensures readers never observe partially written files.
- **Deterministic rollback** through the `.vault-meta/transactions` journal restores original states using verified hashes.
- **Path validation** blocks writes to reserved metadata directories and enforces cross-platform filename safety.
- **Cryptographic plan approval** binds user consent to specific operations, preventing tampering between approval and execution.

## Frequently Asked Questions

### What happens if the system crashes during a vault transaction?

If a crash occurs mid-transaction, the next vault operation detects the incomplete journal in `.vault-meta/transactions` and automatically initiates recovery. The system replays the journal entries, verifying each original file against its stored SHA-256 hash before restoration, ensuring the vault returns to exactly its pre-transaction state without manual intervention.

### How does claude-obsidian handle concurrent modifications from external tools?

External modifications are detected through **pre-condition hash validation**. Before committing any write, the system re-checks the SHA-256 hash of files recorded during the planning phase. If `TransactionConflict` is raised due to hash mismatches, the entire transaction aborts, preventing the external change from being overwritten and avoiding mixed-state corruption.

### Why does the system use directory file descriptors instead of traditional file locks?

Directory file descriptors provide **confinement guarantees** that traditional file locks cannot. Functions like `_open_lock_root_fd()` ensure the process maintains a handle to the specific vault directory inode, preventing Time-of-Check to Time-of-Use (TOCTOU) attacks where an attacker might swap the vault directory itself between validation and write operations.

### Where are transaction journals stored and how long are they retained?

Journals are stored in the `.vault-meta/transactions` directory within the vault root. According to the implementation in `safe_transactions_root`, these files persist until explicitly cleaned up by the recovery system after successful transaction completion. Failed transactions leave journals in place to enable recovery on the next vault access, ensuring no data loss occurs even across process restarts.