sync_vault's forward-pass handler catches ArtifactNotFoundError to surface a deleted-citation conflict as a clean VaultSyncError — but that exception can never reach it, so the handler is dead and the real error escapes as an uncaught traceback.
where — src/vouch/vault_sync.py:467:
try:
r = vault_to_kb(store, vault_dir, actor=actor)
except ArtifactNotFoundError as e:
# A vault edit referenced a claim/entity/source that no longer
# exists in the KB. ... surface it cleanly.
raise VaultSyncError(f"vault edit references unknown artifact: {e}") from e
vault_to_kb files the edit via propose_page (vault_sync.py:389). propose_page validates each cited id and converts the lookup miss to ProposalError, not ArtifactNotFoundError (proposals.py:299-300):
except ArtifactNotFoundError as e:
raise ProposalError(f"unknown claim id: {cid}") from e
The two are disjoint hierarchies — ProposalError(RuntimeError) (proposals.py:36) vs ArtifactNotFoundError(KeyError) (storage.py:74) — so except ArtifactNotFoundError never fires and the ProposalError propagates uncaught.
user-visible effect — I drove vouch sync on a page whose cited claim was deleted:
- today: exit 1 with a raw Python traceback ending
vouch.proposals.ProposalError: unknown claim id: c1. The CLI has a dedicated except VaultSyncError renderer (cli.py:5021) that turns domain errors into a one-line Error: ..., but it's bypassed because the real exception isn't a VaultSyncError.
- expected (and after the fix):
Error: vault edit references unknown artifact: unknown claim id: c1.
repro: mirror a page to a vault, edit its body, delete the claim it cites, run vouch sync --direction forward.
scope note: this does not keep watch_vault alive — that loop catches only KeyboardInterrupt, so it ends on either exception. The fix is specifically about surfacing the documented VaultSyncError (which the CLI renders cleanly) instead of an uncaught ProposalError traceback, and about making the dead handler live.
fix: catch ProposalError alongside ArtifactNotFoundError. happy to send it.
sync_vault's forward-pass handler catchesArtifactNotFoundErrorto surface a deleted-citation conflict as a cleanVaultSyncError— but that exception can never reach it, so the handler is dead and the real error escapes as an uncaught traceback.where —
src/vouch/vault_sync.py:467:vault_to_kbfiles the edit viapropose_page(vault_sync.py:389).propose_pagevalidates each cited id and converts the lookup miss toProposalError, notArtifactNotFoundError(proposals.py:299-300):The two are disjoint hierarchies —
ProposalError(RuntimeError)(proposals.py:36) vsArtifactNotFoundError(KeyError)(storage.py:74) — soexcept ArtifactNotFoundErrornever fires and theProposalErrorpropagates uncaught.user-visible effect — I drove
vouch syncon a page whose cited claim was deleted:vouch.proposals.ProposalError: unknown claim id: c1. The CLI has a dedicatedexcept VaultSyncErrorrenderer (cli.py:5021) that turns domain errors into a one-lineError: ..., but it's bypassed because the real exception isn't aVaultSyncError.Error: vault edit references unknown artifact: unknown claim id: c1.repro: mirror a page to a vault, edit its body, delete the claim it cites, run
vouch sync --direction forward.scope note: this does not keep
watch_vaultalive — that loop catches onlyKeyboardInterrupt, so it ends on either exception. The fix is specifically about surfacing the documentedVaultSyncError(which the CLI renders cleanly) instead of an uncaughtProposalErrortraceback, and about making the dead handler live.fix: catch
ProposalErroralongsideArtifactNotFoundError. happy to send it.