Repository navigation
fix(cli): novelty subcommands exit with a message on unreadable or invalid input - #235
vchamarthi wants to merge 20 commits into
Conversation
…--placebo-text Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
…--atoms file Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
… --novelty or --atoms file Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
…F-8 --queries file Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
…loaded Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
5 open issues
load_view_inputs()only treatsValueErroras 'invalid novelty artifact'. Ifload_artifact()… · Newload_artifact_or_exit()won’t prevent tracebacks for some 'invalid artifact' cases. For example,… · New This re-raise usesfrom exc, which preserves exception chaining. IfProbeFileErrorever escapes… · New Using{exc!r}will print exception class names and quoted strings (e.g.,… · New Theand tmp_path / \"bin.txt\"pattern relies onwrite_bytes()returning a truthy int, which is… · New
What changed in this PR
Improve novelty CLI UX by converting file read/parse failures into short, user-facing error messages (exit 1) instead of tracebacks across multiple subcommands.
Changes:
- Add shared helpers to read inputs (placebo text, view inputs) with explicit error messages and
SystemExit(1). - Update novelty subcommands to use the new helpers (
--placebo-text,--atoms,--artifact,--novelty), and improve routing query loading errors. - Add/adjust tests to assert exit code/message for unreadable/invalid inputs; update changelog and growth baseline.
| File | Description |
|---|---|
| dokimi/dokimi/commands/novelty.py | Switches subcommands to helper-based input reading and early exits on bad inputs. |
| dokimi/dokimi/commands/inputs.py | New shared helper for --flag file reads that exits with a one-line message. |
| dokimi/dokimi/commands/novelty_view_inputs.py | New helper to load novelty view inputs with friendly failures. |
| dokimi/dokimi/novelty/routing.py | Wraps --queries read errors into RoutingInputError with a clear message. |
| dokimi/dokimi/novelty/items.py | Centralizes probe-file JSON read/parse errors into ProbeFileError. |
| dokimi/dokimi/novelty/atoms.py | Adds load_atoms_or_exit for command-friendly atoms loading failures. |
| dokimi/dokimi/novelty/artifact.py | Adds load_artifact_or_exit for command-friendly artifact loading failures. |
| dokimi/tests/test_novelty_cli.py | Adds CLI-level coverage for unreadable --placebo-text and probes file failures. |
| dokimi/tests/test_novelty_routing.py | Adds coverage for unreadable/missing --queries and updates expectations. |
| dokimi/tests/test_novelty_view_inputs.py | New tests for novelty view failure modes (unreadable/invalid files). |
| dokimi/tests/test_novelty_shorten_artifact.py | New tests for shorten’s --artifact load failures. |
| dokimi/tests/test_novelty_atoms_input.py | New tests for --atoms failures in probe/shorten. |
| dokimi/changelog.d/20261025_120000_vchamarthi_cli-input-novelty.md | Changelog entry describing the improved error handling. |
| dokimi/scripts/growth_baseline.json | Updates baseline line count for dokimi/commands/novelty.py. |
Review effort: Lite
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #235 +/- ##
==========================================
+ Coverage 92.97% 92.99% +0.02%
==========================================
Files 242 244 +2
Lines 30745 30868 +123
Branches 5069 5093 +24
==========================================
+ Hits 28585 28706 +121
- Misses 1459 1460 +1
- Partials 701 702 +1
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 7591501. The helpers do what the PR says for the common cases: missing file, directory, non-UTF-8, bad JSON. Three things are worth fixing before merge: one input class that still ends in a traceback, and two gaps in the new tests.
What I ran, on a Linux review host at 7591501:
make ci-local: 29 passed, 0 failed, 7 skipped; 8248 tests passed; the changed-line coverage check passes.- Reproductions for the code finding, with the LLM transport mocked.
- A mutation run over the new code: 38 mutants, 6 of which pass the five test files (findings 2 and 3).
Not caught by this PR (outside this diff, predates it): novelty probe and novelty analyze with --artifact skill:<dir> where <dir>/SKILL.md is a directory still end in IsADirectoryError. check_answer_key (bundle.py:520) and _bundle_from_args catch (FileNotFoundError, ValueError), not OSError. shorten now exits with a message for the same input, so the commands disagree.
Checked, no issues
- Placebo order:
--placebo-textis read before_clients(args)inanalyzeandprobe;--estimate-onlyreturns before it, as on the base. - Callers:
load_probe_cellsandload_querieseach have one caller, and it catches the new error type. The only test expectation that changed isFileNotFoundErrortoRoutingInputErrorfor a missing queries file. - Bot points: Copilot's point on
load_view_inputs()and{}does not apply:load_artifactraisesValueErrorfor that shape, andnovelty view --noveltyon{},[], an empty file and bad JSON exits 1 withError: ... is not a valid novelty artifact. Copilot's point onStopIterationinload_artifact_or_exitdoes not apply: a skill without the closing---reaches themissing a 'description'error, which is caught, and thenext(...)is not reached. Copilot's point onfrom excin_read_probe_filedoes not apply:cmd_novelty_itemscatchesProbeFileErrorand exits with the message, so the chain never reaches a traceback.
|
@exai-bot /review |
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 7591501c56b6, requested by @vchamarthi, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 9 of 93 turns, 0.5 of 48 minutes, 191k in / 3k out tokens.
The PR turns read and parse failures on the novelty input flags into one-line exits. It covers --placebo-text, --probes, --atoms, --novelty, --queries and the shorten --artifact. I traced each change against its callers and found no defects. The exception sets match what the readers raise: ArtifactValidationError and JSONDecodeError are both ValueError, and ProbeFileError and RoutingInputError are caught by their existing handlers. The tests exercise the real commands. I could not run them here because pytest isn't installed, but CI is green.
1 finding is on the changed lines, as comments below.
…ew gaps load_atoms_or_exit returns the parsed record, so probe and shorten stop re-reading the file and exit with the --atoms message when bundle is not an object or its files are not objects. A skill file without a closing frontmatter fence raises ValueError instead of StopIteration. The atoms error in novelty view drops the repr quotes, the probe-file error drops its chain, and tests cover the placebo-before-client order and wrong-shaped atoms. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
…sage The other input-loading exits raise SystemExit with an unprefixed message; match them. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
exai-bot
left a comment
There was a problem hiding this comment.
Note
AI-generated review of 92792f18a38f, requested by @vchamarthi, using the default review skill. It can be wrong: verify a finding before acting on it. React with a thumbs up or down on a finding to tell the bot whether it helped.
Model us.anthropic.claude-sonnet-5-5, effort default, 11 of 94 turns, 0.7 of 48 minutes, 244k in / 4k out tokens.
Re-review of 92792f18a38f: 2 new findings.
1 finding is on the changed lines, as comments below.
Findings outside the diff
1. Minor (confirmed, correctness): novelty probe --artifact still tracebacks on an unreadable entry point
dokimi/dokimi/novelty/bundle.py:520
The changelog says --artifact exits with a message, but only shorten was switched to load_artifact_or_exit. probe goes through check_answer_key, which catches only FileNotFoundError and ValueError. A SKILL.md that exists but cannot be read (PermissionError) would still end in a traceback. This is an edge case; the common missing-file and non-UTF-8 cases are handled.
Suggested fix:
Catch OSError in check_answer_key, or narrow the changelog wording to novelty shorten.
napetrov
left a comment
There was a problem hiding this comment.
Review at 92792f18, follow-up commits pushed up to d2c81f34.
Summary. The earlier rounds (antonwolfy, exai-bot r1, Copilot) are addressed in 26a9916..92792f1. I pushed two small commits for the two exai-bot round-2 items:
fdc190ee(minor, pre-existing code but it contradicts the changelog line for--artifact):check_answer_keyinnovelty/bundle.pycaught onlyFileNotFoundError/ValueError, sonovelty probe --artifactwith an entry point that exists but cannot be read still ended in a traceback. Reproduced withSKILL.mdas a directory (IsADirectoryError); bothexceptclauses now catchOSError. New testtest_a_key_for_an_entry_point_that_cannot_be_read_is_refusedfails before the change and passes after.d2c81f34(nit):test_novelty_atoms_input.pyimportsjsonat module level instead of using__import__("json").
Land-time note. dokimi/scripts/growth_baseline.json is also changed by #233, #236 and #239. Regenerate it on the final base when this lands rather than resolving the conflict by hand.
Test evidence (Linux, uv run): ruff check dokimi tests clean; test_novelty_bundle_key.py test_novelty_atoms_input.py test_novelty_bundle_guards.py test_novelty_cli.py test_novelty_shorten_artifact.py: 144 passed.
From my side this is ready once CI is green on d2c81f34.
…ead of a traceback check_answer_key caught only FileNotFoundError, so a SKILL.md that exists but cannot be read (directory, permission) escaped as an OSError. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at d2c81f3. The new input handling does what the PR says for --placebo-text, --probes, --atoms, --novelty and --queries, and the follow-ups since 7591501 (the atoms bundle check, the OSError in check_answer_key, the placebo-order test) hold. Two things are worth fixing before merge: one --artifact path that still ends in a traceback although the changelog covers it, and one test gap in the new atoms check.
What I ran, on a Linux review host at d2c81f3:
make ci-local: 29 passed, 0 failed, 7 skipped; 8269 tests passed; the changed-line coverage check passes.- Reproductions for the code finding, with the LLM transport mocked.
- A mutation run over the new code: 61 mutants, 11 of which pass the six test files (finding 2 is the one that matters). The six test files all fail against the PR's base source.
Checked, no issues
- Placebo order:
--placebo-textis read before_clients(args)inanalyzeandprobe;--estimate-onlystill returns before it, as on the base. The swap is now caught by the new tests. - Callers:
load_atomskeeps its behaviour for other callers (_atoms_from_recordis a pure refactor);load_probe_cellsandload_querieseach have one caller, and it catches the new error type. - Messages and exit codes: a missing file, a directory, non-UTF-8 and bad JSON give a one-line message and exit 1 for
--placebo-text,--probes,--atoms,--novelty,--queries, and--artifactinprobeandshorten. - Repo rules: SPDX headers on the new files, changelog fragment present,
growth_baseline.jsonmatches the 916 lines ofcommands/novelty.pyandtest_growth_ratchet.pypasses.
… file with a message The novelty analyze, atoms and routing commands now catch OSError, so a directory or a permission error exits with a message instead of a traceback. An atoms file whose top level is not an object is refused as such. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 3709700. The OSError widening at the two --artifact sites and the non-object check in load_atoms_or_exit hold, and the new tests pin them. One thing is worth fixing before merge: an atoms file can still end in a traceback through the bundle record's inner values and through deeply nested JSON (inline).
What I ran, on a Linux review host at 3709700:
make ci-local: 29 passed, 0 failed, 7 skipped; 8281 tests passed; the changed-line coverage check passes.- Reproductions for the code finding, with the LLM transport mocked.
- A mutation run over the changes since d2c81f3: reverting
OSErrortoFileNotFoundErroratcommands/novelty.py:300or:527, or dropping theisinstance(data, dict)check, each fails a new test.
Checked, no issues
- Earlier threads: both points from my review at d2c81f3 are fixed.
analyze,atomsandroutingexit withcannot load ...for aSKILL.mdthat is a directory, and[],nulland5as an atoms file exit withnot an object. - Other reads: I tried a directory, a non-UTF-8 file and a mode-000 file on every
--placebo-text,--probes,--atoms,--novelty,--queriesand--artifact/--competitors/--catalogread in the novelty subcommands. Each exits 1 with a message. - Repo rules: SPDX headers, changelog fragment and
growth_baseline.json(916 lines forcommands/novelty.py) are in order.
…ly nested JSON with a message Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 8c7555e. The new bundle type checks and the RecursionError handling in load_atoms_or_exit and novelty view hold, and the tests added for them fail against the previous source. The claim in the changelog still does not hold for a few inputs: deeply nested JSON in --probes and --queries, and values inside the bundle.excluded rows and the span and atom records of an --atoms file. Three of these end in a traceback (inline). One test gap is also inline.
What I ran, on a Linux review host at 8c7555e:
make ci-local: 29 passed, 0 failed, 7 skipped; 8299 tests passed; the changed-line coverage check passes.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run over the changes since 3709700: 17 mutants, 7 of which pass both new test files (6 are in the test-gap comment; the seventh is equivalent).
Also still tracebacks in --atoms (same class as the excluded comment, outside the bundle block): the span and atom fields are not type-checked in _atoms_from_record (atoms.py:530).
- A span with
"text": 5in ashortenrun raisesTypeError: object of type 'int' has no len()atspans.py:88. - A span with
"start": "a"ornullin a bundle record raisesTypeErrorinentry_point_only(bundle.py:621). - An atom with
"span_ids": [[1]]raisesTypeError: unhashable type: 'list'(bundle.py:624,shorten.py:158). - An atom with
"statement": 5raisesAttributeError: 'int' object has no attribute 'strip'(retention.py:260).
Suggestion: validate these fields once in _atoms_from_record (it raises ValueError, which load_atoms_or_exit and load_view_inputs already turn into the exit), instead of adding one check per reader.
Checked, no issues
- Earlier thread: the
bundlebudgets,resource_roles, rowend/words/role/pathandRecursionErrorpoints from my review at 3709700 are fixed forprobe,shortenandview; the new cases fail against the previous source. - Bundle values: negative, zero and huge
max_*andwords, unknownresource_roles, oddsha256/artifact_namevalues, a one-row or empty-rowfileslist all exit cleanly or run. - Repo rules: SPDX headers, changelog fragments,
growth_baseline.json(916 lines forcommands/novelty.py;test_growth_ratchet.pypasses) and ruff on the changed files are in order.
… query files A bundle.excluded row whose path is not a string now exits with a message instead of raising TypeError in probe. --probes exits with a message on JSON nested too deeply to parse, and --queries reads such a file one query per line. The wrong-shape atoms cases now cover each bundle field separately. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 4813252. The three fixes since 8c7555e hold: a non-string bundle.excluded path, RecursionError in --probes, and RecursionError in --queries, and each new test fails when its guard is reverted. The changelog claim still does not hold for three inputs, one of them the span and atom fields from my last review, which this push did not touch.
What I ran, on a Linux review host at 4813252:
make ci-local: 29 passed, 0 failed, 7 skipped; the changed-line coverage check passes.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run over the changes since 8c7555e: 18 mutants, 16 killed. The two survivors are the nit inline and an
excludedthat is an empty non-list, which is equivalent in effect.
Still open from my review at 8c7555e (not a thread): the span and atom fields of an --atoms file are still not type-checked. See the first inline comment.
Checked, no issues
- Messages and exit codes: a missing file, a directory, non-UTF-8 and
[* 100000 +]* 100000 give a message and exit 1 for--placebo-text,--probes,--atoms,--novelty,--competitorsand--catalogin all novelty subcommands (85 cases). - Queries fallback: a
--queriesfile nested too deeply to parse is read one query per line, like[urgent] build fails, and the new test pins it. - Repo rules: SPDX headers, changelog fragments,
growth_baseline.json(916 lines forcommands/novelty.py) and ruff on the changed files are in order.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
… frontmatter with a message The atoms record reader checks the span text, start and end, and the atom id, question, statement and span_ids once, so probe, shorten and view exit with a message instead of a traceback. An artifact whose SKILL.md frontmatter is nested too deeply to parse now exits with a message at every loader that reads it. A --queries file with a very long integer is read one query per line. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 495d4d1. The three fixes since 4813252 hold: RecursionError at the four artifact loaders, ValueError in load_queries, and the span and atom field checks in _check_record_fields. The changelog claim still does not hold for three inputs: a novelty probe artifact case (a bug) and two input classes that still end in a traceback. The new checks also have a test gap (all inline).
What I ran, on a Linux review host at 495d4d1:
make ci-local: 29 passed, 0 failed, 7 skipped; 8357 tests passed; the changed-line coverage check passes.- Reproductions for the code findings, with the LLM transport mocked.
- A mutation run over the changes since 4813252: 22 mutants, 6 of which pass the six test files (the test-gap comment). The six test files fail against the PR's base source (140 failed).
Checked, no issues
- Earlier points: the
--atomsspan and atom fields, the long-integer--queriesfile, deeply nestedSKILL.mdfrontmatter inanalyze,atoms,routing,shortenand the bundle-lessprobepath, and the boolean budgets are fixed and pinned. DroppingRecursionErrorat each of the four artifact sites, or narrowing theload_queriesclause, fails a test. - Round trip: an atoms file written by
save_atomsstill loads throughload_atomsandload_atoms_or_exit; the new checks reject only shapes the tool never writes. - Repo rules: SPDX headers, changelog fragments,
growth_baseline.json(916 lines forcommands/novelty.py;test_growth_ratchet.pypasses) and ruff are in order.
…eply nested recorded skill with a message Span id, kind, heading_path and protection, atom span_ids, and probe model, artifact, ref_digest, atom_id, probe_kind, arm and verdict are checked for type before use. A recorded bundle whose entry point is nested too deeply to parse is refused as unloadable. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
antonwolfy
left a comment
There was a problem hiding this comment.
A bundle file row with no path still ends in a traceback in novelty shorten, and the other new bundle checks do not cover it. The changes since 495d4d1 hold: the span and atom field checks, the probe-file string checks and RecursionError in check_answer_key each fail a test when reverted, and the six touched test files fail against the PR's base source (186 of 410 tests). One bug and one nit remain, both inline.
What I ran, on a Linux review host at 37360b3:
make ci-local: 29 passed, 0 failed, 7 skipped; the changed-line coverage check passes.- Reproduction for the code finding, with the LLM transport mocked.
- A mutation run over the changes since 495d4d1: 38 mutants, 2 of which pass the touched test files (one is the nit inline; the other is a span without
idorkind, which the reader rejects with a bare'id'message).
Checked, no issues
- Earlier points: the deeply nested recorded
SKILL.mdinprobe, the non-string--probesfields, and the spanid,kind,heading_pathandprotectiontypes are fixed and pinned. - Round trip: an atoms file written by
save_atomsstill loads throughload_atomsandload_atoms_or_exit; an atom with only astatementis accepted, and so are probe files withoutartifactorref_digest. - Sweep: about 5,000 single-field mutations of
--atoms,--noveltyand--probesfiles, plus missing, directory, mode-000, non-UTF-8, empty and deeply nested inputs on every file flag, ended in a message or a clean run. The one traceback is the inline bug.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Replace the per-field bundle, span and atom checks with one table per row type, require a file row's path, check the extraction record, and drop exception catches the loader no longer reaches. Tests pin each field, a null probe model, and the deep-nesting exit on Python 3.14, where json parses the list and the command refuses it. Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com>
Signed-off-by: vchamarthi <123653452+vchamarthi@users.noreply.github.com> # Conflicts: # dokimi/scripts/growth_baseline.json



Summary
noveltysubcommands read user-supplied files (--placebo-text,--probes,--atoms,--novelty,--queries,--artifact) without handling read errors, so a missing, unreadable, non-UTF-8 or invalid file ends in a traceback.Changes
Each affected read now reports the flag, path and reason in a one-line message and exits.
--placebo-textis read before any model client is built, so a typo costs no model calls. One commit per command.Risk
Error paths only.
Testing
make ci-local(offline), Python 3.13: everything passes exceptmypy(1 error) and the 10 tests that also fail onmain(process, sandbox, container probes; macOS). Load-flaky tests pass alone.Checklist
pytestpasses (apart from the 10 pre-existing failures)ruff,mypy, and schema checks pass (mypy: the one pre-existing error).github/.licenserc.yaml)