Skip to content

Commit 4eeda0a

Browse files
mirror29claude
andcommitted
fix(ci): 拒绝截断的审查正文
只有 finish_reason=stop 且正文有效时才发布;length 截断即重试,避免 sticky 评论包含半条 finding。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9a34d78 commit 4eeda0a

2 files changed

Lines changed: 32 additions & 5 deletions

File tree

scripts/deepseek_review.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,10 @@ def _call_deepseek(api_key: str, title: str, diff: str, rules: str) -> str:
141141
if attempt + 1 < MAX_ATTEMPTS:
142142
continue
143143
raise
144-
content = body["choices"][0]["message"].get("content")
145-
if _is_valid_review(content):
146-
return content.strip()
147144
choice = body["choices"][0]
145+
content = choice["message"].get("content")
146+
if choice.get("finish_reason") == "stop" and _is_valid_review(content):
147+
return content.strip()
148148
usage = body.get("usage", {})
149149
reasoning_tokens = usage.get("completion_tokens_details", {}).get(
150150
"reasoning_tokens", "unknown"

tests/test_deepseek_review.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ def _load_module():
2424

2525

2626
class _FakeResponse:
27-
def __init__(self, content: str = "LGTM") -> None:
27+
def __init__(self, content: str = "LGTM", finish_reason: str = "stop") -> None:
2828
self._content = content
29+
self._finish_reason = finish_reason
2930

3031
def __enter__(self):
3132
return self
@@ -35,7 +36,14 @@ def __exit__(self, *_args):
3536

3637
def read(self) -> bytes:
3738
return json.dumps(
38-
{"choices": [{"message": {"content": self._content}}]}
39+
{
40+
"choices": [
41+
{
42+
"finish_reason": self._finish_reason,
43+
"message": {"content": self._content},
44+
}
45+
]
46+
}
3947
).encode()
4048

4149

@@ -74,6 +82,25 @@ def fake_urlopen(request, *, timeout):
7482
self.assertIn("## 项目规则(CLAUDE.md)\nproject rules", payload["messages"][1]["content"])
7583
self.assertEqual(captured["timeout"], module.TIMEOUT_S)
7684

85+
def test_truncated_content_retries_instead_of_publishing_partial_review(self) -> None:
86+
module = _load_module()
87+
responses = [
88+
_FakeResponse("未完成的 finding", finish_reason="length"),
89+
_FakeResponse("完整 review"),
90+
]
91+
92+
with patch.object(
93+
module.urllib.request,
94+
"urlopen",
95+
side_effect=responses,
96+
) as urlopen:
97+
result = module._call_deepseek(
98+
"secret-value", "PR title", "diff body", "project rules"
99+
)
100+
101+
self.assertEqual(result, "完整 review")
102+
self.assertEqual(urlopen.call_count, 2)
103+
77104
def test_empty_content_retries_then_returns_review(self) -> None:
78105
module = _load_module()
79106
responses = [_FakeResponse(""), _FakeResponse("最终 review")]

0 commit comments

Comments
 (0)