Skip to content

feat(review): --post flag for headless GitHub review submission - #36

Merged
andreujuanc merged 5 commits into
masterfrom
feat/headless-post-review
May 25, 2026
Merged

andreujuanc merged 5 commits into
masterfrom
feat/headless-post-review

Conversation

@andreujuanc

Copy link
Copy Markdown
Owner

Summary

  • prr review <N> --post posts the synthesized findings as a single GitHub Review (event=COMMENT) with one inline comment per file+line-anchored finding. Mirrors the TUI's Ctrl+S batch-submit path.
  • Confirms via stdin by default; --yes / -y bypasses the prompt. Non-TTY without --yes fails fast so CI runs don't block on a stdin read.
  • Drive-by: marks the anthropic-direct claude-opus-4-7 row AOI: true. The aoi-benchmark-rework (4e91b34) only flipped the flag on the claude-code Opus 4.7 row; the anthropic-direct row was missed — same model weights, same capabilities.

Context

Motivated by automating PR review across an org's repos via CI — the headless review pipeline already produced findings, but posting them back to GitHub required custom jq + gh api glue. --post removes that step so a workflow can be a single prr review <N> --post --yes.

Verdict is hardcoded to COMMENT for v1; --verdict=auto (trust synth) is a follow-up if it proves useful. prr audit --post is not implemented — audit isn't tied to a PR, so there's no target.

New code lives in internal/review/publish.go (a thin wrapper around the existing git.SubmitReviewWithFindings); the UI's batch-submit path was left untouched to keep the change surgical.

`prr review <N> --post` posts the synthesized findings as a single
GitHub Review (event=COMMENT) with one inline comment per
file+line-anchored finding. Mirrors the TUI's Ctrl+S batch-submit
path so headless and interactive runs produce equivalent reviews.

Confirms via stdin by default; --yes / -y bypasses the prompt and
is required in non-TTY contexts (CI fails fast instead of blocking
on a stdin read).
The aoi-benchmark-rework run in 4e91b34 only flipped AOI: true on
the claude-code Opus 4.7 row; the anthropic-direct row was missed.
Same model weights, same capabilities — surface it in the AOI
picker for users on the direct API.
The synthesis system prompt listed "bug | security | performance |
testing | style | architecture | docs" as allowed categories, but five
of those are not in the embedded category slug set used by
state.Category's UnmarshalJSON. Whenever the LLM took the prompt
literally (e.g. picked "bug" for an error-handling finding), the whole
ReviewOutput parse failed and the headless renderer fell through to
dumping the raw JSON.

Move findings construction out of the LLM: synthesis now writes only
the four narrative fields it actually authors (summary, verdict,
missing_tests, questions_for_author) and the findings array is built
in Go from the post-recheck DeepFindings via BuildFindingsFromDeep —
straight field copy, source_ids back to F-IDs, sorted by severity.

ParseReviewOutput now hard-rejects empty summary and off-list verdict
so the existing retry path engages on bad wrappers; if both attempts
fail, RunSynthesis still returns a usable ReviewOutput with the
deterministic findings and a verdict derived from severity counts.

@andreujuanc andreujuanc left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

AI Review Summary

1 findings across 1 files: 1 low


Posted by prr AI review

Comment thread internal/review/publish.go

@andreujuanc andreujuanc left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

AI Review Summary

3 findings across 2 files: 1 med · 2 low


Posted by prr AI review

}
}
body := formatBatchBody(findings)
if err := git.SubmitReviewWithFindings(prNumber, commitSHA, body, comments); err != nil {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[medium/error-handling] Error not wrapped with context

The error returned from git.SubmitReviewWithFindings is returned directly without wrapping, violating the project's mandatory error wrapping convention.

Suggestion: Wrap the error: return 0, fmt.Errorf("submitting review findings: %w", err)


Posted by prr AI review

}
}
body := formatBatchBody(findings)
if err := git.SubmitReviewWithFindings(prNumber, commitSHA, body, comments); err != nil {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[low/error-handling] Error not wrapped with context

The error returned from git.SubmitReviewWithFindings is not wrapped with context, violating the project's mandatory error wrapping convention.

Suggestion: Wrap the error: return 0, fmt.Errorf("submitting review: %w", err)


Posted by prr AI review

Comment thread internal/ai/review_test.go
runReviewCallWithRetry used to short-circuit on errReviewParse with the
reasoning that "retrying the same prompt won't fix prose-in-response."
In practice LLM sampling variance is wide enough that a second attempt
often produces valid JSON, and losing a 45s deep-review call (with up
to 10 AOIs for grouped calls) to a one-off prose response is too
expensive to leave unrecovered.

Drop the short-circuit so parse failures get the same one-retry
treatment as transient API errors. Context cancellation still
short-circuits.

Also flesh out TestParseReviewOutput_WrapperOnly to assert the
missing_tests and questions_for_author fields parse through (caught
by a review pass against the previous commit).
…questions

The posted GitHub review body used to be a single line of severity
counts. The summary paragraph, missing_tests, and questions_for_author
fields that synthesis already authors weren't being surfaced to the PR
reader — wasted context.

Thread the full *state.ReviewOutput through PostStructuredReview and
publishBatchReview (the TUI's Ctrl+S batch-submit) so both posting
paths render the same enriched body: summary paragraph, severity
counts, then optional Missing tests / Questions for the author
sections. Empty sections are skipped.

@andreujuanc andreujuanc left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

AI Review Summary

The PR introduces a --post flag to the prr review command, enabling headless GitHub review submission, which is a valuable addition for CI automation. While the implementation is generally clean, there is a medium-severity risk of markdown injection in the generated review body that must be addressed before merging.

1 findings across 1 files: 1 med

Missing tests

  • Unit or integration tests for the new functionality in internal/review/publish.go to verify GitHub API interaction.
  • Regression test for the markdown injection vulnerability identified in internal/ui/confirm_modal.go.

Questions for the author

  • How do you plan to sanitize the AI-generated strings (MissingTests, QuestionsForAuthor) before they are interpolated into the markdown body to prevent injection?
  • What is the expected behavior if the GitHub API returns an error during the batch submission process? Is it handled gracefully?
  • Are there any plans to implement --verdict=auto in the near future, or is COMMENT sufficient for current use cases?

Posted by prr AI review

Comment thread internal/ui/confirm_modal.go
@andreujuanc
andreujuanc merged commit 216dd92 into master May 25, 2026
2 checks passed
@andreujuanc
andreujuanc deleted the feat/headless-post-review branch May 25, 2026 18:19
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