fix(open-swe): host-format the review summary, drop agent prose (#1257)

The reviewer agent was writing a 1–2 sentence "top-level take" as the
review body, which produced noisy paragraph-style summaries on PRs
("Reviewed the PR. The new ALLOWED_GITHUB_REPOS allowlist…"). Devin's
review comment is just a one-liner (`✅ No Issues Found` or
`**Devin Review** found N potential issue.`), and was preferred in the
internal A/B vs Graphite.

Drop the `summary` parameter from `publish_review`; render a fixed,
host-formatted body in `render_review_body` instead. Update the reviewer
prompt to forbid prose summaries.
This commit is contained in:
Johannes du Plessis 2026-05-07 15:41:40 -07:00 • committed by GitHub
parent 03d9f23645
commit 88a55057ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 32 additions and 93 deletions

View file

@ -102,12 +102,10 @@ started — you do **not** need to clone, fetch, or check out yourself.
once** at the end of the run. It batches eligible findings into a single once** at the end of the run. It batches eligible findings into a single
GitHub PR Review with inline comments + suggestion blocks, and stores the GitHub PR Review with inline comments + suggestion blocks, and stores the
GitHub comment IDs back so re-reviews can later resolve threads. GitHub comment IDs back so re-reviews can later resolve threads.
- Always pass a `summary` — a 1–2 sentence top-level take that's posted - Do **not** write a summary or top-level take — `publish_review` formats
as the review body above any inline comments. When you found no issues, the review body itself. Your only job is to record findings (or none)
write a short summary that confirms the PR was reviewed and notes and call the tool. Always call it, even when you found no issues, so
anything worth calling out (test coverage, scope, follow-ups). Don't the user gets a "no issues found" comment.
skip the summary just because you have nothing critical to flag — the
summary is how the user knows the review ran.
### Re-reviewing on a new commit ### Re-reviewing on a new commit

View file

@ -4,7 +4,8 @@ The reviewer agent calls ``publish_review`` at the end of a run. That tool
batches eligible findings (severity ≥ threshold, status=open, capped) into a batches eligible findings (severity ≥ threshold, status=open, capped) into a
single GitHub PR Review: single GitHub PR Review:
- Review body: agent-authored summary line. - Review body: a fixed, host-formatted summary line. The agent never writes
prose here — it's either "no issues found" or "found N potential issue(s)".
- Inline comments: one per surfaced finding, anchored to ``path`` + ``line`` - Inline comments: one per surfaced finding, anchored to ``path`` + ``line``
(+ ``start_line`` for ranges) + ``side``. (+ ``start_line`` for ranges) + ``side``.
- Suggestion: when ``finding.suggestion`` is set, appended to the comment body - Suggestion: when ``finding.suggestion`` is set, appended to the comment body
@ -79,41 +80,23 @@ def render_inline_comment_payload(finding: Finding) -> dict[str, Any] | None:
return payload return payload
def render_review_body( def render_review_body(*, pr_number: int, surfaced_count: int) -> str:
*,
pr_number: int,
surfaced_count: int,
total_open_count: int,
severity_threshold: str,
summary: str | None,
) -> str:
"""Compose the top-level review body. """Compose the top-level review body.
Includes the agent's summary (if any) and a footer line so reviewers know Two fixed shapes — no agent prose:
when findings were filtered out below the surfacing threshold.
- 0 surfaced findings: a single "no issues" line.
- N surfaced findings: a single "found N potential issue(s)" line.
""" """
parts: list[str] = [] if surfaced_count == 0:
if surfaced_count == 0 and total_open_count == 0: headline = (
parts.append("**No issues found.**") "## ✅ Open SWE Review: No issues found\n\n"
elif surfaced_count == 0: "Open SWE reviewed this PR and found no potential bugs to report."
parts.append(
f"**No issues at or above `{severity_threshold}` severity.** "
f"({total_open_count} lower-severity finding"
f"{'s' if total_open_count != 1 else ''} hidden.)"
) )
else: else:
finding_word = "finding" if surfaced_count == 1 else "findings" issue_word = "issue" if surfaced_count == 1 else "issues"
parts.append(f"**Found {surfaced_count} {finding_word}.**") headline = f"**Open SWE Review** found {surfaced_count} potential {issue_word}."
hidden = total_open_count - surfaced_count return f"{headline}\n\n<!-- open-swe-reviewer pr={pr_number} -->"
if hidden > 0:
parts.append(
f"_{hidden} lower-severity finding{'s' if hidden != 1 else ''} "
f"below `{severity_threshold}` hidden._"
)
if summary:
parts.append(summary.strip())
parts.append(f"<!-- open-swe-reviewer pr={pr_number} -->")
return "\n\n".join(p for p in parts if p)
async def post_pull_request_review( async def post_pull_request_review(

View file

@ -29,7 +29,6 @@ from ..utils.github_token import get_github_token
def publish_review( def publish_review(
summary: str | None = None,
severity_threshold: str = "medium", severity_threshold: str = "medium",
cap: int = 4, cap: int = 4,
) -> dict[str, Any]: ) -> dict[str, Any]:
@ -44,7 +43,8 @@ def publish_review(
at ``cap`` to avoid review spam. at ``cap`` to avoid review spam.
3. POST a single GitHub PR Review with the eligible findings as inline 3. POST a single GitHub PR Review with the eligible findings as inline
comments. ``finding.suggestion`` becomes a ```suggestion``` block comments. ``finding.suggestion`` becomes a ```suggestion``` block
(the "Commit suggestion" UX). (the "Commit suggestion" UX). The review body is a fixed,
host-formatted summary line — you do not write it.
4. Store the returned per-comment IDs back on each finding so a future 4. Store the returned per-comment IDs back on each finding so a future
re-review can resolve those threads on GitHub when the issues are fixed. re-review can resolve those threads on GitHub when the issues are fixed.
5. For findings whose status moved ``open`` → ``resolved`` since the last 5. For findings whose status moved ``open`` → ``resolved`` since the last
@ -53,9 +53,6 @@ def publish_review(
6. Update ``last_reviewed_sha`` on the thread to the current head SHA. 6. Update ``last_reviewed_sha`` on the thread to the current head SHA.
Args: Args:
summary: Optional 1–2 sentence top-level take on the PR. Rendered as
the review body. Skip if you have nothing useful to say beyond
the per-finding comments.
severity_threshold: Lowest severity to surface to GitHub (default severity_threshold: Lowest severity to surface to GitHub (default
``medium``). Lower-severity findings stay in state and surface in ``medium``). Lower-severity findings stay in state and surface in
the future UI but not on the PR. the future UI but not on the PR.
@ -96,7 +93,6 @@ def publish_review(
pr_number=pr_number, pr_number=pr_number,
head_sha=head_sha, head_sha=head_sha,
token=token, token=token,
summary=summary,
severity_threshold=_cast_severity(severity_threshold), severity_threshold=_cast_severity(severity_threshold),
cap=cap, cap=cap,
) )
@ -114,7 +110,6 @@ async def _publish_review_async(
pr_number: int, pr_number: int,
head_sha: str, head_sha: str,
token: str, token: str,
summary: str | None,
severity_threshold: Severity, severity_threshold: Severity,
cap: int, cap: int,
) -> dict[str, Any]: ) -> dict[str, Any]:
@ -145,9 +140,6 @@ async def _publish_review_async(
review_body = render_review_body( review_body = render_review_body(
pr_number=pr_number, pr_number=pr_number,
surfaced_count=len(inline_comments), surfaced_count=len(inline_comments),
total_open_count=len(open_unpublished),
severity_threshold=severity_threshold,
summary=summary,
) )
review_response = await post_pull_request_review( review_response = await post_pull_request_review(

View file

@ -67,56 +67,24 @@ def test_render_inline_comment_payload_returns_none_for_file_level() -> None:
assert payload is None assert payload is None
def test_render_review_body_includes_summary_and_marker() -> None: def test_render_review_body_with_findings_uses_potential_issue_phrasing() -> None:
body = render_review_body( body = render_review_body(pr_number=123, surfaced_count=2)
pr_number=123, assert body.startswith("**Open SWE Review** found 2 potential issues.")
surfaced_count=2,
total_open_count=3,
severity_threshold="medium",
summary="LGTM with two notes",
)
assert "LGTM with two notes" in body
assert "<!-- open-swe-reviewer pr=123 -->" in body assert "<!-- open-swe-reviewer pr=123 -->" in body
assert "Found 2 findings" in body
assert "1 lower-severity finding" in body
def test_render_review_body_surfaces_no_findings_message() -> None: def test_render_review_body_singular_finding() -> None:
body = render_review_body( body = render_review_body(pr_number=123, surfaced_count=1)
pr_number=99, assert body.startswith("**Open SWE Review** found 1 potential issue.")
surfaced_count=0,
total_open_count=0,
severity_threshold="medium", def test_render_review_body_no_findings_message() -> None:
summary=None, body = render_review_body(pr_number=99, surfaced_count=0)
) assert "## ✅ Open SWE Review: No issues found" in body
assert "No issues found" in body assert "Open SWE reviewed this PR and found no potential bugs to report." in body
assert "<!-- open-swe-reviewer pr=99 -->" in body assert "<!-- open-swe-reviewer pr=99 -->" in body
def test_render_review_body_no_findings_keeps_agent_summary() -> None:
body = render_review_body(
pr_number=42,
surfaced_count=0,
total_open_count=0,
severity_threshold="medium",
summary="Reviewed PR — clean refactor, well-tested.",
)
assert "No issues found" in body
assert "Reviewed PR — clean refactor, well-tested." in body
def test_render_review_body_no_surfaced_with_hidden_lower_severity() -> None:
body = render_review_body(
pr_number=7,
surfaced_count=0,
total_open_count=2,
severity_threshold="medium",
summary=None,
)
assert "No issues at or above `medium` severity" in body
assert "2 lower-severity findings hidden" in body
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_resolve_review_thread_returns_true_on_success() -> None: async def test_resolve_review_thread_returns_true_on_success() -> None:
response = MagicMock() response = MagicMock()
@ -182,7 +150,6 @@ async def test_publish_review_skips_findings_already_published() -> None:
pr_number=7, pr_number=7,
head_sha="sha", head_sha="sha",
token="t", token="t",
summary=None,
severity_threshold="medium", severity_threshold="medium",
cap=15, cap=15,
) )
@ -222,7 +189,6 @@ async def test_publish_review_posts_summary_when_no_findings() -> None:
pr_number=7, pr_number=7,
head_sha="sha", head_sha="sha",
token="t", token="t",
summary=None,
severity_threshold="medium", severity_threshold="medium",
cap=15, cap=15,
) )