diff --git a/src/kb/cli.py b/src/kb/cli.py index 4816c5f..a70b50e 100644 --- a/src/kb/cli.py +++ b/src/kb/cli.py @@ -1904,6 +1904,13 @@ def correct( except ValueError as e: click.echo(f"Error: {e}", err=True) raise SystemExit(1) from None + except OSError as e: + # Batch write failed mid-apply; apply_corrections rolled the files back (#1 COW). + click.echo( + f"Error: correction failed and was rolled back — no files were changed ({e}).", + err=True, + ) + raise SystemExit(1) from None finally: kb.close() diff --git a/tests/test_correct.py b/tests/test_correct.py index d160c0d..a4d7dad 100644 --- a/tests/test_correct.py +++ b/tests/test_correct.py @@ -369,6 +369,20 @@ def _invoke(self, runner: CliRunner, memory_tree: Path, args: list[str]): with patch("kb.cli._find_project_root", return_value=project_root): return runner.invoke(cli, ["correct", *args], catch_exceptions=False) + def test_apply_rollback_shows_graceful_error( + self, cli_runner: CliRunner, memory_tree: Path, monkeypatch + ): + """A rolled-back batch (OSError from apply) surfaces a clean message, not a traceback (#1).""" + from kb.api import KnowledgeBase + + def boom(self, *a, **k): # type: ignore[no-untyped-def] + raise OSError("disk full (simulated)") + + monkeypatch.setattr(KnowledgeBase, "correct_term", boom) + result = self._invoke(cli_runner, memory_tree, ["Quartz Indexer", "Datalux", "--apply"]) + assert result.exit_code == 1 + assert "rolled back" in result.output.lower() + def test_scan_json_output(self, cli_runner: CliRunner, memory_tree: Path): """kbx correct TERM --json returns structured scan output.""" result = self._invoke(cli_runner, memory_tree, ["Quartz Indexer", "--json"])