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:
-
Platform Guard –
_require_write_platform()immediately aborts on native Windows systems that lack directory-descriptor confinement, preventing symlink attacks. -
Vault Validation – The target path must exist as a directory; otherwise a
TransactionValidationErroris raised. -
Bundle Loading – The bundle is parsed via
_load_bundleand must carry theclaude-obsidian.transaction.v1schema identifier. -
Operation ID Sanitization –
safe_operation_idenforces a filesystem-safe identifier using only alphanumerics, hyphens, dots, and underscores, capped at 128 bytes. -
Approval Verification – If
approved_plan_sha256is supplied, the bundle’s pre-lock approval hash (produced byinspect_bundle) must match exactly, preventing "plan-drift" between review and execution. -
Mutation Lock – A
MutationLockis acquired on the vault root using directory file descriptors (on WSL/Linux/macOS), ensuring no directory swap can occur during the operation. -
Runtime Store –
_RuntimeStore.from_lockcreates a temporary workspace at.vault-meta/transactions/<operation-id>/to hold journals, backups, and intermediate files. -
Idempotency Check – If the operation ID already contains a completed
changed-paths.json, the previous result is returned immediately, making re-application safe. -
Metadata Expansion –
_expand_managed_metadatainjects automatic writes for internal bookkeeping files such as.raw/.manifest.json. -
Write Preparation – Each
PreparedWriteis inspected for portable path rules, duplicate key checks, and hash consistency. -
Atomic File Writes –
_atomic_vault_writewrites each file to a temporary.txn-*name and then callsos.replace(), guaranteeing that observers never see partially written files. -
Journal Finalization – Upon success,
journal.jsonandchanged-paths.jsonare 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.jsonfile. - approved_plan_sha256 (
str | None): The SHA-256 hash frominspect_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_bundlefunction inclaude_obsidian/transaction.pyexecutes a 12-step safety protocol including platform guards, mutation locks, and atomic file writes. - Use
claude-obsidian inspectfollowed byclaude-obsidian applyfor CLI workflows, or importapply_bundlefor Python automation. - Always provide
approved_plan_sha256except during initial vault setup to prevent plan-drift attacks. - The system guarantees atomicity via temporary
.txn-*files andos.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →