Summary
stop_status() reports healthy, completed transaction journals as "unreadable" whenever the journal exceeds 64 KiB. The journals are valid JSON with "state": "complete" — they are simply larger than the probe's read cap, so the truncated slice fails to parse.
The warning cannot be cleared by the command it recommends, and the count grows permanently as a vault accumulates large transactions.
Root cause
claude_obsidian/hook_adapter.py:270 reads a bounded prefix and immediately parses it as complete JSON:
payload = _bounded_regular_bytes(root, journal, 64 * 1024)
...
state = strict_json_loads(payload.decode("utf-8")).get("state")
except (UnicodeDecodeError, json.JSONDecodeError, ValueError):
unreadable_journals += 1
_bounded_regular_bytes returns handle.read(limit + 1) (line 98), so any journal over 64 KiB yields a truncated 65537-byte slice, strict_json_loads raises JSONDecodeError, and the file is counted as unreadable.
The probe only needs the state key, but it pays the cost of parsing the entire document.
Why 64 KiB is too small
The same package explicitly permits transactions far larger than this cap allows.
claude_obsidian/transaction.py:130 sets MAX_TRANSACTION_WRITES = 1024, enforced at lines 3061, 3071, and 3533. Observed journal size scales at roughly 350–450 bytes per write entry, so a transaction at the sanctioned ceiling produces a journal in the region of 400 KB — about 6× the probe's limit.
In practice the probe fails at roughly 150 writes, or ~15% of the documented maximum. Measured from a real vault:
| journal bytes |
writes |
readable by probe |
| 124,820 |
278 |
no |
| 111,548 |
275 |
no |
| 50,659 |
145 |
yes (barely) |
| 25,076 |
63 |
yes |
Batched operations are documented usage — skills/wiki-ingest/SKILL.md describes ingest as "a single source or bounded batch" and lists "batch ingest" among its triggers — so ordinary use reaches this size.
Worth noting that every other bound in hook_adapter.py is a named module constant (MAX_CONTEXT_BYTES, MAX_STATUS_BYTES, MAX_STATUS_ITEMS, MAX_TRANSACTION_SCAN), while this one is an inline 64 * 1024 at the call site — it reads like a generic small-file bound applied to a file the package guarantees can be much larger.
Reproduction
- Apply any transaction with more than ~150 writes (or hand-write a journal over 64 KiB with
"state": "complete" and a matching valid changed-paths.json).
- Trigger the Stop hook, or call the probe directly:
from pathlib import Path
from claude_obsidian.hook_adapter import stop_status
print(stop_status(start=Path("/path/to/vault")))
# CLAUDE_OBSIDIAN_STATUS: 1 unreadable transaction journal(s) detected.
# Run `claude-obsidian transaction recover` before the next mutation.
- Confirm the journal is actually fine:
import json
raw = open(journal_path, "rb").read()
print(len(raw), json.loads(raw)["state"]) # 124820 complete
json.loads(raw[:65537].decode("utf-8")) # JSONDecodeError — what the probe sees
claude-obsidian transaction recover --vault <vault> returns {"recovered": [], ...} and the warning persists.
Second issue: the recommended remedy cannot apply
The message directs the user to run claude-obsidian transaction recover. That command cannot resolve this state.
_recover_incomplete_locked (transaction.py:3958) acts only on journals whose state is in {"prepared", "applying", "rollback-failed"}. A journal that is already complete with a valid changed-paths.json hits the continue at line 3956 and is never touched. Since these journals are complete — and only unreadable because of the size cap — the prescribed command is a guaranteed no-op.
This is arguably the more damaging half of the bug. The warning is persistent, self-diagnosing in a way that points somewhere wrong, and the suggested fix silently succeeds while changing nothing. In our case two separate sessions concluded the vault was corrupt and invented an out-of-tree transactions-archive/ directory to hide the oversized journals from os.scandir — a workaround that only worked because the scan does not recurse. Nothing prunes transaction directories, so the count only ever grows.
Suggested fix
Any of these would resolve it:
- Raise the limit to match what transactions may legitimately produce —
MAX_TRANSACTION_RUNTIME_JSON_BYTES (8 MiB, transaction.py:132) is already the package's bound for runtime JSON of this kind, and would be a consistent choice.
- Extract
state without parsing the whole document, so journal size stops mattering to a probe that wants one key.
- Distinguish "too large to probe" from "corrupt" in the warning text, and drop the
transaction recover suggestion for states that command cannot address.
The last point matters independently of the size cap: whatever the limit becomes, a warning should not recommend a command that provably cannot clear it.
Environment
- claude-obsidian 2.0.0
- Python 3.14.6
- macOS (Darwin 25.5.0), arm64
Summary
stop_status()reports healthy, completed transaction journals as "unreadable" whenever the journal exceeds 64 KiB. The journals are valid JSON with"state": "complete"— they are simply larger than the probe's read cap, so the truncated slice fails to parse.The warning cannot be cleared by the command it recommends, and the count grows permanently as a vault accumulates large transactions.
Root cause
claude_obsidian/hook_adapter.py:270reads a bounded prefix and immediately parses it as complete JSON:_bounded_regular_bytesreturnshandle.read(limit + 1)(line 98), so any journal over 64 KiB yields a truncated 65537-byte slice,strict_json_loadsraisesJSONDecodeError, and the file is counted as unreadable.The probe only needs the
statekey, but it pays the cost of parsing the entire document.Why 64 KiB is too small
The same package explicitly permits transactions far larger than this cap allows.
claude_obsidian/transaction.py:130setsMAX_TRANSACTION_WRITES = 1024, enforced at lines 3061, 3071, and 3533. Observed journal size scales at roughly 350–450 bytes per write entry, so a transaction at the sanctioned ceiling produces a journal in the region of 400 KB — about 6× the probe's limit.In practice the probe fails at roughly 150 writes, or ~15% of the documented maximum. Measured from a real vault:
Batched operations are documented usage —
skills/wiki-ingest/SKILL.mddescribes ingest as "a single source or bounded batch" and lists "batch ingest" among its triggers — so ordinary use reaches this size.Worth noting that every other bound in
hook_adapter.pyis a named module constant (MAX_CONTEXT_BYTES,MAX_STATUS_BYTES,MAX_STATUS_ITEMS,MAX_TRANSACTION_SCAN), while this one is an inline64 * 1024at the call site — it reads like a generic small-file bound applied to a file the package guarantees can be much larger.Reproduction
"state": "complete"and a matching validchanged-paths.json).claude-obsidian transaction recover --vault <vault>returns{"recovered": [], ...}and the warning persists.Second issue: the recommended remedy cannot apply
The message directs the user to run
claude-obsidian transaction recover. That command cannot resolve this state._recover_incomplete_locked(transaction.py:3958) acts only on journals whose state is in{"prepared", "applying", "rollback-failed"}. A journal that is alreadycompletewith a validchanged-paths.jsonhits thecontinueat line 3956 and is never touched. Since these journals arecomplete— and only unreadable because of the size cap — the prescribed command is a guaranteed no-op.This is arguably the more damaging half of the bug. The warning is persistent, self-diagnosing in a way that points somewhere wrong, and the suggested fix silently succeeds while changing nothing. In our case two separate sessions concluded the vault was corrupt and invented an out-of-tree
transactions-archive/directory to hide the oversized journals fromos.scandir— a workaround that only worked because the scan does not recurse. Nothing prunes transaction directories, so the count only ever grows.Suggested fix
Any of these would resolve it:
MAX_TRANSACTION_RUNTIME_JSON_BYTES(8 MiB,transaction.py:132) is already the package's bound for runtime JSON of this kind, and would be a consistent choice.statewithout parsing the whole document, so journal size stops mattering to a probe that wants one key.transaction recoversuggestion for states that command cannot address.The last point matters independently of the size cap: whatever the limit becomes, a warning should not recommend a command that provably cannot clear it.
Environment