fix(noema): report the actual rejected location, not just an array position - #1671
Conversation
validate_substantive_verdict()'s reviewed_lines/probes loops enumerate
from 1 and raise "Noema reviewed line {index} is not an exact
changed-side line" / "Noema adversarial probe {index} is not an exact
changed-side line". {index} is the entry's array position, not a
source-code line number, but the message reads exactly like a citation
to literal file line N. The rejected (path, line, side) tuple the
model actually submitted, and the diff's real changed-line set, were
never included anywhere - only the bare index.
This made every occurrence of this failure org-wide undiagnosable from
CI output alone. Confirmed live on ContextualWisdomLab/naruon#1503,
where the check fired twice ("line 3" and "line 1") and both looked
like source-line citations before turning out to be array positions,
with no way to tell from the logs whether the model cited a wrong
line, an off-by-one, a wrong path, or the wrong LEFT/RIGHT side (job
ids 99740003119, 99740827824, 99742973829, 99745529545, 99746600989,
99748382284, 99873344797).
Changes:
- New _entry_ordinal() renders "entry N/total (array index N-1, not a
source line)" in place of the bare index, keeping the fixed "Noema
reviewed line "/"Noema adversarial probe " prefix so
_stable_failure_diagnostic()'s trusted-prefix allowlist still passes
these messages through unredacted during repair.
- New _format_location() and _nearby_changed_locations() add the
actual rejected path/line/side plus up to 5 nearest real changed
locations on the same path, so a wrong path, a wrong side, or an
off-by-one line is now visible directly in the raised message and
the GitHub Actions ::error:: annotation it becomes.
- Both enumerate(..., start=1) loops (reviewed_lines and probes) are
the only two in this file with this pattern; both are covered.
Investigated changed_diff_locations() and the diff/prompt-construction
path for a systematic off-by-one per the task brief: verified against
a real multi-hunk `git diff` (context lines, a pure insertion, a
replacement, a new file) that every computed (path, line, side)
matches the actual file content exactly, and confirmed the exact same
`diff` string is used both to compute locations and to build the
model prompt, with the JSON schema example itself using the
prefix-stripped path convention the validator expects. No location-
computation bug found; only the reporting gap above was fixed.
Also updates the two contract-style assertions in
test_substantive_verdict_fail_closed_boundaries that pinned the old
"reviewed line 1 must be an object" / "probe 1 must be an object"
text, and adds new coverage for the helpers and for the enriched
end-to-end rejection messages (including no-hint and >5-neighbors
cases) to keep scripts/ci at 100% line+branch coverage and 100%
docstring coverage.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ErwZSYW3pm585NiM3Q7aN
|
Warning Review limit reachedNext included review available in 2 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Team Run ID: 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
|
The bug
scripts/ci/noema_review_gate.py'svalidate_substantive_verdict()has two loops that validate the Noema LLM reviewer's JSON verdict cites real changed-diff lines:and the analogous loop for
probes.indexhere is the entry's array position withinreviewed_lines/probes— not a source-code line number — but the message text ("Noema reviewed line 3 is not an exact changed-side line") reads exactly like a citation to literal file line 3. Worse, neither loop ever included the actual rejected(path, line, side)tuple the model submitted, nor anything from the diff's real changed-line set, in the raised message.This was originally investigated on
ContextualWisdomLab/naruon#1503, where the check fired twice — once as "Noema reviewed line 3 is not an exact changed-side line" and once as "Noema reviewed line 1 is not an exact changed-side line" (job ids99740003119,99740827824,99742973829,99745529545,99746600989,99748382284,99873344797). Both were initially misread as the LLM hallucinating a citation to literal file line 1 or 3, before tracing into this code and realizing "1" and "3" were just array positions, with the actual submitted path/line/side nowhere visible in the CI logs.Since this required workflow gates every PR org-wide, every occurrence of this failure — in any sibling repo — has been undiagnosable from CI output alone until now.
What changed
In
scripts/ci/noema_review_gate.py:_entry_ordinal(position, total)— renders"entry N/total (array index N-1, not a source line)"in place of the bare index. It keeps the fixed"Noema reviewed line "/"Noema adversarial probe "prefix so_stable_failure_diagnostic()'s trusted-prefix allowlist still passes these messages through unredacted on the repair-retry path (that allowlist keys off the literal prefix text, so it did not need to change)._format_location(path, line, side)—repr()s the raw rejected values so a wrong path, a wrong side,None, or a non-int line is visible and unambiguous in the message._nearby_changed_locations(locations, path, line)— adds up to 5 of the nearest real changed locations sharing the same path, sorted by distance from the cited line, so an off-by-one or near-miss citation is obvious at a glance. Empty when the path doesn't appear in the diff at all.Example, before vs. after (same underlying failure — an approve verdict citing
tool.py:99 RIGHTwhen the diff only touched line 2):Both
enumerate(..., start=1)loops in this file follow the fixed pattern above and are now identical in shape; a repo-wide grep confirmed these are the only two occurrences of this pattern innoema_review_gate.py.Investigated but ruled out: a location-computation bug
Per the task brief, I also checked
changed_diff_locations()and the diff/prompt-construction path for a systematic off-by-one or path-stripping bug that could produce a wrong location rather than just report it poorly. I built a real multi-hunkgit diff(context lines, a pure insertion, a line replacement near the end of a hunk, a second modified file, and a new file) and verified every(path, line, side)tuplechanged_diff_locations()computed against the actual file content — all matched exactly, no off-by-one. I also confirmed the exact samediffstring is used both to compute the location set and to build the model's prompt (so the model is never shown a diff view that disagrees with what's validated), and that the JSON schema example embedded in the prompt uses the same prefix-stripped path convention ("file.py", not"a/file.py") that the validator expects. No provable location-computation bug found — only the reporting gap above was fixed, and the validation itself was not loosened.Tests
test_substantive_verdict_fail_closed_boundariesthat pinned the old"reviewed line 1 must be an object"/"probe 1 must be an object"exact text.test_entry_ordinal_names_an_array_position_not_a_line_number,test_format_location_reprs_every_raw_field, andtest_nearby_changed_locations_covers_every_branch(non-string path, no-match path, int-line distance sort, non-int-line fallback sort, and the+N moretruncation branch) for the three new helpers directly.test_validate_substantive_verdict_reports_rejected_location_and_nearby_hintandtest_validate_substantive_verdict_probe_rejection_reports_location_and_hintfor the end-to-end enriched messages, including the no-hint case (cited path never touched by the diff).Verified locally (Python 3.12, matching this repo's target):
Constraints respected
develop/mainchanges, no force-push, no merge — this PR is left open for the org's normal review/merge automation.🤖 Generated with Claude Code
https://claude.ai/code/session_016ErwZSYW3pm585NiM3Q7aN
Generated by Claude Code