Skip to content

fix(code-review): codex reviewers can answer, and a no-verdict review stops suppressing - #87

Merged
pedromvgomes merged 6 commits into
mainfrom
fix/codex-review-issues
Sep 9, 2026
Merged

fix(code-review): codex reviewers can answer, and a no-verdict review stops suppressing#87
pedromvgomes merged 6 commits into
mainfrom
fix/codex-review-issues

Conversation

@pedromvgomes

Copy link
Copy Markdown
Owner

Two bugs that compound: no codex reviewer could answer, and the empty review it posted then blocked the re-run. Together they made agtk code-review run --pr on this repo's default roster produce a pull request that displays a review, having reviewed nothing, and refuse to try again.

Every codex reviewer died on the answer schema

correctness-codex#1 ran and did not answer: codex ended the turn:
  "code": "invalid_json_schema",
  "message": "Invalid schema for response_format 'codex_output_schema': ... 'required' is
    required to be supplied and to be an array including every key in properties.
    Missing 'suggestion'."

OpenAI strict structured-output mode refuses a schema where a key in properties is absent from required, and refuses it at the provider rather than at the model — so the run dies having read nothing. findingSchema and judgeSchema both declared suggestion and omitted it from required.

suggestion is now ["string", "null"] and required, in both — the spelling start_line and end_line already used. Required keeps the schema valid; nullable keeps a reviewer with nothing to suggest from inventing one. A null decodes to the empty string every consumer already guards on.

Claude accepts either spelling, so this was green on the worktree roster (claudecode) and fatal on the PR roster (codex) — the only place it ran.

internal/reviewrun/schema_test.go walks each schema sent to a provider and asserts generically that every key in a properties appears in that object's required, reading the JSON rather than listing today's field names.

A review that reached no verdict suppressed the review that would

Suppression counted any App-authored review of the head, so a run where every reviewer died posted a review saying so, and that review then refused the re-run. Only --force got past it.

A head now counts as reviewed only when a review this App authored carries a marker reading verdict=complete. The data was posted all along and never read back: the reviews query asked for each review's commit and authorship and not its body.

  • An App-authored review carrying no parseable marker counts as no verdict — the safer reading: being wrong costs one re-run, the other direction costs a pull request displaying a review nobody performed.
  • Only ByViewer reviews count; anyone who can review a PR can type the characters that open a marker.
  • --force is unchanged for a genuinely complete review.

The check reads ReadSubmittedReviews, the reader approval already used, and ReadReviewedCommits is gone. A second query against the same connection for the same reviews — one of which could not answer the question — was what the cheaper body-less query bought.

Verification

Both diagnoses were confirmed against the live provider before fixing: the old schema is rejected with a 400 naming suggestion, the new shape is accepted and returns "suggestion": null. PR #86's two posted reviews both carry verdict=incomplete at the current head.

make check green. Reviewed locally with the deep panel over the branch, then again over the fix commit — clean.

Release notes at docs/releases/v0.12.1.md.

…nswer

OpenAI strict structured-output mode refuses a schema where a key in
`properties` is absent from `required`, and refuses it at the provider
rather than at the model, so the run dies having read nothing:

  Invalid schema for response_format 'codex_output_schema': ...
  'required' ... including every key in properties. Missing 'suggestion'.

findingSchema and judgeSchema both declared `suggestion` and omitted it
from `required`. It becomes `["string", "null"]` and joins `required`, the
spelling start_line and end_line already used: required keeps the schema
valid, nullable keeps a reviewer with nothing to suggest from inventing
one. A null decodes to the empty string every consumer already guards on.

Claude accepts either spelling, so this was green on the worktree roster
and fatal on the PR roster, which is the only place it ran.

The new test walks each schema generically and fails on any properties key
missing from required, so a schema that gains a field fails here rather
than on the next pull request review.
Suppression counted any review this App authored of the head, so a run
where every reviewer died posted a review saying so and that review then
refused the re-run. The pull request displayed a review, had reviewed
nothing, and the marker recording the failure was what prevented anyone
fixing it. Only --force got past it.

A head counts as reviewed only when a review the App authored carries a
marker reading verdict=complete. The data was posted all along and never
read back: the reviews query asked for each review's commit and authorship
and not its body, so the marker was out of reach.

ReadReviewedCommits becomes ReadPriorReviews, returning each authored
review with its head and its body. It parses nothing — the transport does
not own the review format, and the caller already holds it.

An App-authored review carrying no parseable marker counts as no verdict.
It is the safer of the two readings: being wrong costs one re-run, where
the other direction costs a pull request displaying a review nobody
performed.

--force is unchanged for a genuinely complete review.
…has bodies

The verdict check added a second GraphQL query returning each authored
review's commit and body. ReadSubmittedReviews already returned exactly
that, for approval, and pages at reviewsPerPage because a review body is
the largest document on a pull request — where the new query inherited
pageSize, sized for reads that carry no body, and could overrun the
response limit and fail the read.

Both readers now come through ReadSubmittedReviews, and the CLI filters on
ByViewer. The comment justifying two functions by one of them needing no
body went with the function: the run needs the body now, so the cheaper
query could not answer the question it was there to answer.
Three candidates, each anchored to the file it came from: the strict-mode
required-key rule that made every posted review die silently, what a
posted review has to carry before it suppresses the next one, and why a
body-carrying GraphQL query has its own page size.

Replaces an earlier candidate describing the suppression defect, which
named two symbols this branch deletes.
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

bulwark

  • scan — no findings

  • coverage — go: 81.3% (baseline 81.3%); go patch: 100.0% (10/10 new lines; baseline 81.3%)

📦 Full bulwark output — complete scan and coverage logs.

@agtk-code-review agtk-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by agtk — panel standard-codex

1 RED. 1 posted as inline comments, 0 against a whole file.

What's good

  • Suggestion fields are required but nullable, preserving optional advice while satisfying strict schema requirements.
  • Generic schema tests catch missing required properties in nested objects and verify null suggestions decode safely.
  • Reusing the submitted-review reader removes duplicate query code and preserves pagination sized for review bodies.

Ran and reported nothing: security-codex

Absent from the reviewed copy (1): a symlink, a submodule or a file too large to read is not code this review looked at.

panel standard-codex, 4 runs, 1 convention docs read · range main...b6d2843f9edadefd8d5aadd2d07c216a446675df · manifest built-in default

Comment thread internal/cli/codereview_pr.go Outdated
Suppression accepted any App-authored complete review of the head, where
approval reads the newest whose commit and marker head both match. The two
disagreed in both directions.

An older complete review spoke for a newer forced run that failed, so
suppression refused the re-review while approval refused the head, and the
only way out was a --force nothing told anyone to type — the bug this
branch set out to fix, rebuilt one condition further along. A marker naming
another commit also counted, though a body can be edited after it is posted
and approval rejects exactly that.

lastReview becomes reviewapprove.LastReview and carriesAVerdictFor calls
it. Two commands turning on one question must not answer it twice.

@agtk-code-review agtk-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by agtk — panel standard-codex

This review did not reach a verdict: no reviewer answered, so nothing looked at this change

Could not answer (2)

This review is partial: what these would have found is unknown, not absent.

Absent from the reviewed copy (1): a symlink, a submodule or a file too large to read is not code this review looked at.

panel standard-codex, 2 runs, 2 could not answer, 1 convention docs read · range main...875b6f6adc8d97823b4de532735a5e198a41f6aa · manifest built-in default

@agtk-code-review agtk-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by agtk — panel standard

No findings survived the panel.

Ran and reported nothing: correctness, security

Absent from the reviewed copy (1): a symlink, a submodule or a file too large to read is not code this review looked at.

panel standard, 3 runs, 1 convention docs read, $2.2861 · range main...875b6f6adc8d97823b4de532735a5e198a41f6aa · manifest built-in default

@agtk-code-review agtk-code-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved by agtk

875b6f6adc8d97823b4de532735a5e198a41f6aa carries a review by this installation that reached a verdict. Every finding it reports at or above AMBER is marked a false positive on its thread by an account that can push, and every comment thread on this pull request is resolved.

A person typed this. No review run can reach it, and nothing overrides it: a finding is cleared by changing the code or by a written statement on its thread, and by nothing else.

@pedromvgomes
pedromvgomes merged commit a1c825f into main Sep 9, 2026
10 checks passed
@pedromvgomes
pedromvgomes deleted the fix/codex-review-issues branch September 9, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant