fix: close issues after merged PRs

This commit is contained in:
2026-05-06 17:33:16 +08:00
parent 70a17d6675
commit 3c624cc46d
3 changed files with 70 additions and 9 deletions

View File

@@ -292,12 +292,23 @@ class Database:
SELECT t.* SELECT t.*
FROM tasks t FROM tasks t
JOIN issues i ON i.repo_id = t.repo_id AND i.issue_number = t.issue_number JOIN issues i ON i.repo_id = t.repo_id AND i.issue_number = t.issue_number
WHERE t.state = ? WHERE t.state != ?
AND t.pr_number IS NOT NULL AND t.pr_number IS NOT NULL
AND i.state = 'open' AND i.state = 'open'
AND (
t.state IN (?, ?)
OR t.lease_owner IS NULL
OR t.lease_expires_at IS NULL
OR t.lease_expires_at < ?
)
ORDER BY t.id ORDER BY t.id
""", """,
(TaskState.HUMAN_REVIEW_READY.value,), (
TaskState.CANCELLED.value,
TaskState.HUMAN_REVIEW_READY.value,
TaskState.FAILED.value,
dt_to_db(utcnow()),
),
).fetchall() ).fetchall()
return [self._task(row) for row in rows] return [self._task(row) for row in rows]

View File

@@ -10,7 +10,7 @@ from .agents import CommandRunner, read_report, render_command, write_prompt
from .config import AppConfig from .config import AppConfig
from .db import Database, PullRequestFeedbackCursor from .db import Database, PullRequestFeedbackCursor
from .gitea import GiteaClient, GiteaComment, GiteaPullReview from .gitea import GiteaClient, GiteaComment, GiteaPullReview
from .models import IssueRecord, RepositoryRecord, TaskRecord, TaskState from .models import ACTIVE_STATES, IssueRecord, RepositoryRecord, TaskRecord, TaskState
from .rendering import ( from .rendering import (
parse_review_report, parse_review_report,
render_human_review_summary, render_human_review_summary,
@@ -92,12 +92,12 @@ def close_issues_for_merged_pull_requests(db: Database, client: GiteaClient) ->
) )
client.close_issue(owner=repo.owner, name=repo.name, issue_number=issue.issue_number) client.close_issue(owner=repo.owner, name=repo.name, issue_number=issue.issue_number)
db.update_issue_state(task.repo_id, task.issue_number, "closed") db.update_issue_state(task.repo_id, task.issue_number, "closed")
db.add_event( message = f"closed issue #{issue.issue_number} after merged PR #{task.pr_number}"
task.id, if task.state in ACTIVE_STATES:
task.state, db.clear_pr_feedback_pending(task.id)
task.state, db.transition(task.id, TaskState.CANCELLED, message=message, clear_lease=True)
f"closed issue #{issue.issue_number} after merged PR #{task.pr_number}", else:
) db.add_event(task.id, task.state, task.state, message)
closed += 1 closed += 1
return closed return closed

View File

@@ -656,6 +656,56 @@ def test_close_issues_for_merged_pull_requests_skips_unmerged_pr(db):
assert db.get_issue(repo.id, 1).state == "open" # type: ignore[union-attr] assert db.get_issue(repo.id, 1).state == "open" # type: ignore[union-attr]
def test_close_issues_for_merged_pull_requests_handles_queued_feedback_task(db):
repo = db.upsert_repository(
owner="acme",
name="service",
clone_url="https://gitea.test/acme/service.git",
default_branch="main",
enabled=True,
)
db.upsert_issue(
repo_id=repo.id,
issue_number=1,
title="Ready issue",
body="Body",
labels=["agent:ready"],
state="open",
html_url="https://gitea.test/acme/service/issues/1",
)
task = db.create_task(repo.id, 1)
task = transition_to_human_review_ready(db, task.id, pr_number=5, branch_name="agent/issue-1-ready-issue")
db.mark_pr_feedback_pending(task.id)
db.transition(
task.id,
TaskState.DISCOVERED,
message="queued PR feedback from 1 human comment(s)",
clear_lease=True,
)
requests: list[tuple[str, str, dict]] = []
def handler(request: httpx.Request) -> httpx.Response:
payload = json.loads(request.content.decode() or "{}")
requests.append((request.method, request.url.path, payload))
if request.url.path == "/api/v1/repos/acme/service/pulls/5":
return httpx.Response(200, json={"number": 5, "state": "closed", "merged": True})
if request.url.path == "/api/v1/repos/acme/service/issues/1/comments":
return httpx.Response(201, json={"id": 1})
if request.url.path == "/api/v1/repos/acme/service/issues/1":
return httpx.Response(200, json={"number": 1, "state": "closed"})
return httpx.Response(404)
closed = close_issues_for_merged_pull_requests(db, make_client(handler))
updated_task = db.get_task(task.id)
assert closed == 1
assert db.get_issue(repo.id, 1).state == "closed" # type: ignore[union-attr]
assert updated_task is not None
assert updated_task.state == TaskState.CANCELLED
assert not db.has_pending_pr_feedback(task.id)
assert ("PATCH", "/api/v1/repos/acme/service/issues/1", {"state": "closed"}) in requests
def test_run_task_no_diff_becomes_blocked(db, tmp_path): def test_run_task_no_diff_becomes_blocked(db, tmp_path):
config = make_config(tmp_path) config = make_config(tmp_path)
seed_task(db) seed_task(db)