Transaction System in claude-obsidian: Architecture and Implementation

The transaction system in claude-obsidian guarantees recoverable, operation-level vault updates through schema validation, process-wide file locking, atomic write operations, and deterministic journal-based rollback mechanisms.

The claude-obsidian repository implements a robust transaction layer designed to manage Obsidian vault modifications with ACID-like properties. At its core, the transaction system in claude-obsidian coordinates every write operation through a strict lifecycle: pre-flight validation, exclusive locking, durable journaling, atomic execution, and post-operation verification. This architecture ensures that crashes or conflicts never leave the vault in an inconsistent state.

Core Transaction Schemas and Validation

Every transaction begins with strict schema enforcement. The system defines three primary schemas in claude_obsidian/transaction.py to standardize operation envelopes and audit trails.

Transaction Schemas

The module declares BUNDLE_SCHEMA, RESULT_SCHEMA, and JOUNRAL_SCHEMA (lines 41-44) to validate the structure of operation bundles, their execution results, and the durable journal entries. These schemas enforce type safety and prevent malformed operations from reaching the execution phase.

Error Hierarchy

The system centralizes exception handling through a dedicated error hierarchy. TransactionError serves as the base exception (lines 54-61), carrying a machine-readable code and human-readable message. Specializations include:

  • TransactionConflict: Raised when concurrent modifications violate consistency
  • TransactionValidationError: Signals schema or path validation failures
  • TransactionRecoveryError: Aggregates errors during rollback operations

This hierarchy allows callers to distinguish between recoverable validation issues and critical system failures.

Safe Path Handling and Security

Before any file operation executes, the system validates all paths through a suite of security functions in claude_obsidian/transaction.py.

Path Normalization and Validation

The _normalize_vault_path function (lines 54-71) canonicalizes vault-relative paths, while _assert_portable_write_path (lines 88-103) rejects illegal characters, symlink traversal attempts, and case-fold aliases that could enable directory traversal attacks. Supporting utilities like _safe_vault_path and _safe_directory ensure that every write target remains strictly within the vault boundary, preventing escape from the designated root directory.

Atomic Operations and Concurrency Control

The transaction system prevents race conditions and partial writes through process-wide locking and atomic file operations.

MutationLock for Process-Wide Exclusivity

The MutationLock class (lines 145-185) implements exclusive access control by acquiring an on-disk lock at .vault-meta/mutation.lock. When apply_bundle executes, it instantiates this lock with a configurable timeout, blocking until the vault becomes available. The lock guarantees that only one process can modify the vault root and metadata namespace at any given time.

Atomic File Writes

To eliminate the "partial-write" problem, the system uses atomic_write and _atomic_vault_write (around line 1249). These functions write data to a temporary file and perform a guaranteed os.replace (atomic rename) to move the file into its final position only after the complete content is persisted to disk.

Journal-Based Recovery System

Durability and recoverability depend on a write-ahead journal that records every intended modification before execution.

Journal Creation and Verification

When apply_bundle initiates, the _write_journal function (around line 4000) persists a journal.json file containing the complete operation plan. After execution, _validate_journal compares the journal against the actual filesystem state, ensuring every expected modification occurred. The journal schema tracks file creation, modification, and deletion operations with sufficient metadata to enable reconstruction.

Deterministic Rollback Mechanism

If validation fails or a crash interrupts execution, _restore_journal (lines 4022-4086) executes a deterministic rollback. This function walks the journal in reverse order, utilizing _confined_vault_unlink and _atomic_vault_write to undo changes safely. Errors during recovery are aggregated and reported as TransactionRecoveryError, ensuring administrators receive complete diagnostic information.

Public API and Workflow

The transaction system exposes a streamlined interface through apply_bundle, which orchestrates the entire lifecycle.

Bundle Planning and Approval

Before execution, clients generate a deterministic approval hash using plan_approval_sha256 (lines 126-136). This function computes a SHA-256 hash of the expanded bundle combined with the vault identity and prepared write list, creating an auditable fingerprint that must be reviewed before the operation proceeds.

from pathlib import Path
from claude_obsidian.transaction import plan_approval_sha256, safe_operation_id

vault = Path("/path/to/vault")
bundle = {
    "schema": "claude-obsidian.operation.v1",
    "operation_type": "save",
    "payload": {"pages": ["foo.md", "bar.md"]},
}

op_id = safe_operation_id("my-save-run")
approval_hash = plan_approval_sha256(vault, bundle, [])
print(f"Approve this bundle with hash: {approval_hash}")

Applying Transactions

The apply_bundle function (lines 4433-4500) serves as the primary entry point for the CLI, SDK, and test suites. It validates the bundle, acquires the MutationLock, writes the journal, performs atomic writes, verifies the final state against the approval hash, and cleans up the lock.

from claude_obsidian.transaction import apply_bundle, MutationLock

lock = MutationLock(vault_root=vault, timeout=30.0)
if not lock.acquire():
    raise RuntimeError("Could not obtain mutation lock.")

result = apply_bundle(
    vault_root=vault,
    operation=bundle,
    approved_plan_sha256=approval_hash,
)
print("Transaction result:", result["status"])

Manual Recovery Operations

While recovery typically executes automatically, administrators can trigger manual rollback using _restore_journal when holding an active lock:

from claude_obsidian.transaction import _restore_journal
import json

journal_path = vault / ".vault-meta/transactions" / "my-save-run" / "journal.json"
journal = json.loads(journal_path.read_text())

_restore_journal(vault_root=vault, lock=lock, runtime=None, journal=journal)

Implementation Files and Dependencies

The transaction system spans several modules within the repository:

  • claude_obsidian/transaction.py: Core implementation containing schemas, the MutationLock class, atomic I/O primitives, and the apply_bundle entry point
  • claude_obsidian/paths.py: Low-level helpers for canonical path resolution, directory file descriptor flags, and platform-specific safety checks
  • claude_obsidian/json_utils.py: Provides parse_finite_json_float for strict JSON parsing within transaction boundaries
  • claude_obsidian/cli.py: Command-line interface that constructs bundles, displays approval hashes, and invokes the transaction system

Summary

  • Schema Validation: Uses BUNDLE_SCHEMA, RESULT_SCHEMA, and JOURNAL_SCHEMA to enforce structure and prevent malformed operations
  • Security: Implements _normalize_vault_path and related functions to block directory traversal and symlink attacks
  • Concurrency: The MutationLock class provides process-wide exclusive access through filesystem-based locking
  • Atomicity: File writes occur via temporary files and atomic rename operations to eliminate partial writes
  • Durability: Write-ahead journaling with _write_journal enables _restore_journal to perform deterministic rollback of failed operations
  • Entry Point: The apply_bundle function orchestrates validation, locking, execution, and verification as the primary public API

Frequently Asked Questions

How does claude-obsidian prevent partial writes during transactions?

The system uses atomic file operations implemented in atomic_write and _atomic_vault_write. These functions write content to temporary files and perform an atomic os.replace only after the complete data is persisted. Combined with the MutationLock that prevents concurrent modifications, this ensures that no observer ever sees partially written content.

What happens if a transaction fails midway through execution?

If execution interrupts after the journal is written but before completion, the system detects the incomplete state during the next operation or through explicit validation. The _restore_journal function reads the durable journal.json and rolls back changes in reverse order, using _confined_vault_unlink to remove created files and _atomic_vault_write to restore original content.

How does the MutationLock ensure cross-process safety?

The MutationLock class creates a dedicated lock directory at .vault-meta/mutation.lock and verifies ownership on every operation. Because the lock relies on the filesystem's atomic directory creation semantics and is checked by all transaction entry points, it guarantees that only one process can hold write access to the vault root and metadata namespace at any time, regardless of process boundaries.

What is the purpose of the plan_approval_sha256 hash?

The plan_approval_sha256 function generates a deterministic cryptographic hash of the expanded bundle, vault identity, and prepared write list. This creates an immutable audit trail and requires explicit user or system approval before apply_bundle executes. The approval mechanism prevents unauthorized or accidental modifications by ensuring the executed operation matches the reviewed plan exactly.

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 →