fix(cli): translate domain errors into clean ClickException output#31
Conversation
The CLI handlers for approve and reject caught only (ArtifactNotFoundError, ValueError) but proposals.approve() / proposals.reject() raise ProposalError -- a RuntimeError subclass. The four propose-* shortcuts caught nothing at all. Result: a double- approve, an empty rejection reason, an empty claim text, or an unknown source id surfaced a raw Python traceback instead of the one-line `Error: ...` the rest of the CLI emits. Add a small `_cli_errors` context manager that translates ArtifactNotFoundError / ValueError / ProposalError / LifecycleError into click.ClickException, and apply it to every command that calls into proposals, lifecycle, sessions, or storage. The MCP and JSONL servers already do the equivalent in their own envelopes; this brings the human-facing surface in line. Adds tests/test_cli.py with regressions for approve, reject, propose- claim, propose-entity, and show.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughAdds a reusable ChangesCLI Error Handling Centralization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/vouch/cli.py (1)
14-48:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix import ordering to unblock CI.
Ruff
I001is failing on this import block, so the PR cannot merge until imports are sorted/formatted.Suggested fix
-from collections.abc import Iterator -from contextlib import contextmanager +from collections.abc import Iterator +from contextlib import contextmanager ... -from .lifecycle import LifecycleError -from .models import ProposalStatus -from .proposals import ( - ProposalError, - approve as do_approve, -) +from .lifecycle import LifecycleError +from .models import ProposalStatus +from .proposals import ProposalError, approve as do_approveThen run:
ruff check src/vouch/cli.py --fix🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/vouch/cli.py` around lines 14 - 48, The import block is not sorted per Ruff's I001 rule; reorder the imports into standard-library, third-party, then local-package groups and alphabetize within each group (e.g., keep collections.abc, contextlib, pathlib first, then click/yaml, then all vouch package imports), combine related vouch imports into grouped blocks and remove duplicate grouping (e.g., consolidate the several from .proposals and . imports), ensure symbols like build_context_pack, LifecycleError, ProposalStatus, propose_claim/propose_entity/propose_page/propose_relation, do_approve/do_reject, KBStore/discover_root are still imported, and then run ruff check src/vouch/cli.py --fix to apply formatting automatically.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/vouch/cli.py`:
- Around line 14-48: The import block is not sorted per Ruff's I001 rule;
reorder the imports into standard-library, third-party, then local-package
groups and alphabetize within each group (e.g., keep collections.abc,
contextlib, pathlib first, then click/yaml, then all vouch package imports),
combine related vouch imports into grouped blocks and remove duplicate grouping
(e.g., consolidate the several from .proposals and . imports), ensure symbols
like build_context_pack, LifecycleError, ProposalStatus,
propose_claim/propose_entity/propose_page/propose_relation,
do_approve/do_reject, KBStore/discover_root are still imported, and then run
ruff check src/vouch/cli.py --fix to apply formatting automatically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 24761a5b-57bd-44d4-b94f-5d95d137d341
📒 Files selected for processing (2)
src/vouch/cli.pytests/test_cli.py
The CI workflow runs `ruff check`, `mypy`, then `pytest`, and the fix/cli-clean-domain-errors branch was failing all three on pre-existing main-branch issues unrelated to the CLI change itself. * ruff: add `from e` to the seven `raise ValueError(...) from FileExistsError` re-raises added by the recent exclusive-create guards in storage.put_* (B904), and let ruff re-sort the cli.py import block (I001). * mypy: narrow the `kind` value flowing into `ContextItem(type=...)` with a `Literal` cast so the strict-typed field accepts what the search backends actually return. * pytest: `session_end()` mutates a session that `session_start()` has already written, but `put_session()` now uses exclusive create and rejects the second write. Add `KBStore.update_session()` mirroring `update_claim` and have `session_end` call it; existing test_sessions coverage now passes again. No behavior change for the CLI surface — these are infrastructure fixes so the existing test_sessions / lint / type assertions pass on this branch.
|
Thanks for going beyond the report and tackling the whole bug class — the |
fix(cli): translate domain errors into clean ClickException output
Summary
vouchCLI command that calls into proposals / lifecycle / sessions / storage now produces a one-lineError: …for domain errors instead of a Python traceback. Closes the bug originally reported againstapprove/rejectand the wider class it belongs to.Root Cause
src/vouch/cli.py:194 (approve) and src/vouch/cli.py:207 (reject) caught only
(ArtifactNotFoundError, ValueError), butproposals.approve()/proposals.reject()raiseProposalError, which subclassesRuntimeError— so it slipped past the except and surfaced as a traceback. The fourpropose-*shortcuts (cli.py:223, cli.py:241, cli.py:259, cli.py:274) had no error handling at all, so any validation failure ("claim text is empty","unknown source/evidence id", etc.) also printed a traceback. The lifecycle and session commands had the same gap. The MCP and JSONL servers (server.py:359-363 / jsonl_server.py:445-457) already get this right — the CLI was the outlier.Fix
Added a small
_cli_errorscontext manager that translates(ArtifactNotFoundError, ValueError, ProposalError, LifecycleError)intoclick.ClickException, and applied it to every command that calls into the domain layer:show,approve,reject,propose-claim,propose-page,propose-entity,propose-relation,source add,supersede,contradict,archive,confirm,cite,session end,crystallize.import-applykeeps its existingexcept RuntimeErrorsincebundle.import_applyraises bareRuntimeErrorfor conflicts and that broader catch was intentional.No behavior change on the happy path; only the failure rendering differs (
Error: …instead of a traceback, exit code 1).Test Plan
tests/test_cli.pyusingclick.testing.CliRunner:test_approve_already_decided_proposal_shows_clean_error(the originally-reportednot pendingcase)test_reject_empty_reason_shows_clean_errortest_propose_claim_empty_text_shows_clean_errortest_propose_claim_unknown_source_shows_clean_errortest_propose_entity_empty_name_shows_clean_errortest_show_missing_proposal_shows_clean_error(regression for the existing handling — must keep working after refactor)showone already passed since it caughtArtifactNotFoundError).pytest tests/test_cli.py tests/test_storage.py tests/test_jsonl_server.py tests/test_index.py -q→ 45 passed.ruff check src/vouch/cli.py tests/test_cli.py→ All checks passed.Fixes fix(cli): approve and reject commands don't catch ProposalError — raw traceback #29
Summary by CodeRabbit
Bug Fixes
Tests