How to Apply a Transaction Bundle in Claude-Obsidian: CLI and Python Guide

Apply a transaction bundle in Claude-Obsidian by running claude-obsidian apply with the --approved-plan-sha256 flag, or call apply_bundle() from claude_obsidian/transaction.py with the vault path, bundle file, and approval hash to atomically mutate your vault with guaranteed rollback safety.

Claude-Obsidian (AgriciDaniel/claude-obsidian) treats every knowledge-changing operation as a transaction bundle—a JSON document that describes file mutations, cryptographic digests, and approval hashes. Applying this bundle is the final step that actually commits changes to your vault while ensuring atomicity, reproducibility, and cross-platform safety.

Understanding the Transaction Bundle Architecture

A transaction bundle is a JSON artifact that follows the claude-obsidian.transaction.v1 schema. It encapsulates the operation type, target files, expected content digests, and a cryptographic approval hash generated during inspection. When you apply a bundle, the system validates every precondition before touching disk, then performs atomic writes to prevent partial updates.

The core logic resides in claude_obsidian/transaction.py inside the apply_bundle function. This implementation orchestrates a 12-step safety protocol that guards against plan drift, concurrent mutations, and platform-specific vulnerabilities.

The Apply Bundle Safety Workflow

Before any file is modified, apply_bundle executes a series of guarded validations:

  1. Platform Guard – _require_write_platform() immediately aborts on native Windows systems that lack directory-descriptor confinement, preventing symlink attacks.

  2. Vault Validation – The target path must exist as a directory; otherwise a TransactionValidationError is raised.

  3. Bundle Loading – The bundle is parsed via _load_bundle and must carry the claude-obsidian.transaction.v1 schema identifier.

  4. Operation ID Sanitization – safe_operation_id enforces a filesystem-safe identifier using only alphanumerics, hyphens, dots, and underscores, capped at 128 bytes.

  5. Approval Verification – If approved_plan_sha256 is supplied, the bundle’s pre-lock approval hash (produced by inspect_bundle) must match exactly, preventing "plan-drift" between review and execution.

  6. Mutation Lock – A MutationLock is acquired on the vault root using directory file descriptors (on WSL/Linux/macOS), ensuring no directory swap can occur during the operation.

  7. Runtime Store – _RuntimeStore.from_lock creates a temporary workspace at .vault-meta/transactions/<operation-id>/ to hold journals, backups, and intermediate files.

  8. Idempotency Check – If the operation ID already contains a completed changed-paths.json, the previous result is returned immediately, making re-application safe.

  9. Metadata Expansion – _expand_managed_metadata injects automatic writes for internal bookkeeping files such as .raw/.manifest.json.

  10. Write Preparation – Each PreparedWrite is inspected for portable path rules, duplicate key checks, and hash consistency.

  11. Atomic File Writes – _atomic_vault_write writes each file to a temporary .txn-* name and then calls os.replace(), guaranteeing that observers never see partially written files.

  12. Journal Finalization – Upon success, journal.json and changed-paths.json are written to the runtime store, and the mutation lock is released.

If any step fails, the function calls _confined_vault_unlink to roll back partially created files and raises a specific TransactionError subclass.

How to Apply a Transaction Bundle via CLI

The command-line interface in claude_obsidian/cli.py wraps the Python API with interactive safety checks. You must first inspect the bundle to obtain its approval hash, then apply it with that hash to prove the bundle has not changed since review.

Inspect the bundle to retrieve the approval hash:

claude-obsidian inspect --bundle my-change.json --vault /path/to/vault

Apply the bundle using the hash returned by the inspect command:

claude-obsidian apply \
  --bundle my-change.json \
  --approved-plan-sha256 <approval-hash-from-inspect> \
  --vault /path/to/vault

The --approved-plan-sha256 flag is mandatory unless you are initializing a new vault during setup. The CLI ultimately invokes apply_bundle(vault, bundle_path, approved_plan_sha256=...).

Programmatic Application with Python API

For automation or plugin development, import apply_bundle directly from claude_obsidian/transaction.py. You may optionally call inspect_bundle first to extract metadata programmatically.

from pathlib import Path
from claude_obsidian.transaction import apply_bundle, inspect_bundle

vault = Path("/path/to/vault")
bundle_path = Path("my-change.json")

# Optional: inspect first to extract the approval hash

inspection = inspect_bundle(vault, bundle_path)
approval_hash = inspection["approval_sha256"]

# Apply the bundle atomically

result = apply_bundle(
    vault_root=vault,
    bundle_or_path=bundle_path,
    approved_plan_sha256=approval_hash,
)

print("Transaction status:", result["status"])
print("Changed files:", result["changed_paths"])

Key arguments for apply_bundle:

  • vault_root (Path): Path to the target vault directory.
  • bundle_or_path (Path | dict): Either a loaded bundle dictionary or a path to a .json file.
  • approved_plan_sha256 (str | None): The SHA-256 hash from inspect_bundle; omit only during initial vault setup.
  • timeout / stale_after (int): Lock acquisition timeout (default 10s) and lock expiration (default 1h).
  • fail_after (int | None): Testing hook to simulate crashes after N writes.
  • progress (Callable): Callback receiving (stage, count) tuples for UI feedback.

Handling Transaction Errors and Recovery

The transaction system distinguishes between validation failures, conflicts, and recovery errors. Always wrap apply_bundle in a try-except block to handle these specific cases:

from claude_obsidian.transaction import (
    apply_bundle,
    TransactionValidationError,
    TransactionConflict,
    TransactionRecoveryError,
)

try:
    apply_bundle(vault, bundle_path, approved_plan_sha256=approval_hash)
except TransactionValidationError as exc:
    print(f"Invalid bundle or vault: {exc.code}")
except TransactionConflict as exc:
    print(f"Operation ID reuse or state drift: {exc.code}")
except TransactionRecoveryError as exc:
    print(f"Rollback failed: {exc.code}")

Each exception carries a machine-readable code attribute (e.g., "PLAN_CHANGED", "OPERATION_ID_REUSED") that is also logged in the transaction journal for forensic debugging. If a failure occurs after partial writes, the system automatically invokes rollback procedures to restore the vault to its pre-transaction state.

Source files implementing this behavior include claude_obsidian/vault_ops.py for high-level vault initialization, and the test suite in tests/test_transaction.py which verifies idempotency and plan-approval enforcement. Windows compatibility tests in tests/test_windows_compat.py ensure that apply_bundle refuses to run on unsupported native Windows platforms.

Summary

  • Transaction bundles are immutable JSON descriptions of vault changes that require an approval hash to apply.
  • The apply_bundle function in claude_obsidian/transaction.py executes a 12-step safety protocol including platform guards, mutation locks, and atomic file writes.
  • Use claude-obsidian inspect followed by claude-obsidian apply for CLI workflows, or import apply_bundle for Python automation.
  • Always provide approved_plan_sha256 except during initial vault setup to prevent plan-drift attacks.
  • The system guarantees atomicity via temporary .txn-* files and os.replace, with automatic rollback on any failure.

Frequently Asked Questions

What is a transaction bundle in Claude-Obsidian?

A transaction bundle is a JSON document conforming to the claude-obsidian.transaction.v1 schema that describes a set of file write operations, their expected content digests, and metadata. It serves as a portable, verifiable plan for mutating an Obsidian vault, enabling atomic and reproducible changes across different machines.

Why does apply_bundle require an approval hash?

The approved_plan_sha256 parameter prevents "plan-drift" by ensuring the bundle file has not been modified between the time you reviewed it (using inspect_bundle) and the time you apply it. If the hash mismatches, TransactionConflict is raised immediately before any disk mutation occurs, protecting against tampering or accidental edits.

What happens if a transaction fails mid-way?

If any step in the 12-step workflow fails, apply_bundle catches the error, invokes _confined_vault_unlink to remove any partially written .txn-* files, and raises a specific TransactionError subclass. The vault remains in its original state due to the atomic write strategy that uses temporary files and os.replace only after all validations pass.

Can I use Claude-Obsidian on native Windows?

No. The _require_write_platform() guard in claude_obsidian/transaction.py explicitly aborts on native Windows because the platform lacks directory-file-descriptor confinement required for safe mutation locking. You must run Claude-Obsidian on WSL, Linux, or macOS to apply transaction bundles.

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 →