codeant-ai-for-open-source[bot] commented on code in PR #40823:
URL: https://github.com/apache/superset/pull/40823#discussion_r3366611729


##########
scripts/devin_autofix/report.py:
##########
@@ -0,0 +1,1000 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Generate analytics reports from GitHub metadata for devin-autofix runs.
+
+Produces:
+  - automation_report.html
+  - automation_runs.csv
+  - automation_runs.sqlite
+
+Usage:
+  python -m scripts.devin_autofix.report --output-dir ./report_output
+"""
+
+from __future__ import annotations
+
+import argparse
+import csv
+import json
+import os
+import pathlib
+import sqlite3
+from datetime import datetime, timezone
+from typing import Any
+
+import requests
+
+from scripts.devin_autofix.github_state import (
+    get_issue_comments,
+    GITHUB_API,
+    parse_marker,
+    REPO,
+    RunMarker,
+)
+
+AUTOFIX_LABELS = [
+    "devin-autofix/in-progress",
+    "devin-autofix/waiting-review",
+    "devin-autofix/returned-human",
+    "devin-autofix/merged",
+    "devin-autofix/reverted",
+    "devin-autofix/complete",
+]
+
+
+def _headers() -> dict[str, str]:
+    token = os.environ["GITHUB_TOKEN"]
+    return {
+        "Authorization": f"token {token}",
+        "Accept": "application/vnd.github+json",
+        "X-GitHub-Api-Version": "2022-11-28",
+    }
+
+
+def _search_issues_with_autofix_labels() -> list[dict[str, Any]]:
+    """Find all issues that have any devin-autofix label."""
+    seen: set[int] = set()
+    results: list[dict[str, Any]] = []
+
+    for label in AUTOFIX_LABELS:
+        page = 1
+        while True:
+            resp = requests.get(
+                f"{GITHUB_API}/repos/{REPO}/issues",
+                headers=_headers(),
+                params={
+                    "labels": label,
+                    "state": "all",
+                    "per_page": str(100),
+                    "page": str(page),
+                },
+                timeout=30,
+            )
+            resp.raise_for_status()
+            batch = resp.json()
+            if not batch:
+                break
+            for issue in batch:
+                if issue["number"] not in seen and "pull_request" not in issue:
+                    seen.add(issue["number"])
+                    results.append(issue)
+            page += 1
+
+    return results
+
+
+def _get_all_markers_for_issue(issue_number: int) -> list[RunMarker]:
+    """Get the latest marker per run_id for an issue (captures retries)."""
+    comments = get_issue_comments(issue_number)
+    latest_by_run: dict[str, RunMarker] = {}
+    for comment in comments:
+        marker = parse_marker(comment.get("body", ""))
+        if marker and marker.run_id:
+            latest_by_run[marker.run_id] = marker
+    return list(latest_by_run.values())
+
+
+def _get_pr_details(pr_number: int) -> dict[str, Any] | None:
+    """Fetch PR details."""
+    try:
+        resp = requests.get(
+            f"{GITHUB_API}/repos/{REPO}/pulls/{pr_number}",
+            headers=_headers(),
+            timeout=30,
+        )
+        resp.raise_for_status()
+        result: dict[str, Any] = resp.json()
+        return result
+    except requests.HTTPError:
+        return None
+
+
+def _get_pr_reviews(pr_number: int) -> list[dict[str, Any]]:
+    """Fetch reviews for a PR."""
+    try:
+        resp = requests.get(
+            f"{GITHUB_API}/repos/{REPO}/pulls/{pr_number}/reviews",
+            headers=_headers(),
+            params={"per_page": "100"},
+            timeout=30,
+        )
+        resp.raise_for_status()
+        result: list[dict[str, Any]] = resp.json()
+        return result
+    except requests.HTTPError:
+        return []
+
+
+def _get_pr_checks(pr_number: int) -> list[dict[str, str]]:
+    """Fetch check runs for a PR head commit."""
+    try:
+        resp = requests.get(
+            f"{GITHUB_API}/repos/{REPO}/pulls/{pr_number}",
+            headers=_headers(),
+            timeout=30,
+        )
+        resp.raise_for_status()
+        pr = resp.json()
+        head_sha = pr["head"]["sha"]
+
+        checks_resp = requests.get(
+            f"{GITHUB_API}/repos/{REPO}/commits/{head_sha}/check-runs",
+            headers=_headers(),
+            params={"per_page": "100"},
+            timeout=30,
+        )
+        checks_resp.raise_for_status()
+        check_runs = checks_resp.json().get("check_runs", [])
+        return [
+            {
+                "name": cr["name"],
+                "conclusion": cr.get("conclusion") or cr.get("status", 
"pending"),
+            }
+            for cr in check_runs
+        ]
+    except requests.HTTPError:
+        return []
+
+
+def _parse_iso(ts: str | None) -> datetime | None:
+    """Parse an ISO timestamp string."""
+    if not ts:
+        return None
+    try:
+        return datetime.fromisoformat(ts.replace("Z", "+00:00"))
+    except ValueError:
+        return None
+
+
+def _seconds_between(start: datetime | None, end: datetime | None) -> int | 
None:
+    if start and end:
+        return int((end - start).total_seconds())
+    return None
+
+
+def _format_duration(seconds: int | None) -> str:
+    """Format seconds into a human-readable duration with appropriate units."""
+    if seconds is None:
+        return "N/A"
+    if seconds < 60:
+        return f"{seconds}s"
+    if seconds < 3600:
+        return f"{seconds / 60:.1f} min"
+    if seconds < 86400:
+        return f"{seconds / 3600:.1f} hr"
+    if seconds < 604800:
+        return f"{seconds / 86400:.1f} days"
+    return f"{seconds / 604800:.1f} weeks"
+
+
+def _resolve_status(marker: RunMarker, issue_labels: set[str]) -> str:
+    """Determine final status from marker status and issue labels."""
+    if "devin-autofix/merged" in issue_labels:
+        return "merged"
+    if "devin-autofix/complete" in issue_labels:
+        return "merged"
+    if "devin-autofix/reverted" in issue_labels:
+        return "reverted"
+    if marker.status == "complete":
+        return "merged"
+    return marker.status
+
+
+def _classify_ci(checks: list[dict[str, str]]) -> str:
+    """Classify overall CI result from individual check conclusions."""
+    if not checks:
+        return "N/A"
+    passed_conclusions = ("success", "skipped")
+    failed = sum(1 for c in checks if c["conclusion"] == "failure")
+    if failed > 0:
+        return "failed"
+    if all(c["conclusion"] in passed_conclusions for c in checks):
+        return "passed"
+    return "pending"
+
+
+def _get_issue_type(issue: dict[str, Any]) -> str:
+    """Extract issue type label."""
+    type_labels = ("bug", "feature", "refactor", "docs")
+    return next(
+        (lbl["name"] for lbl in issue.get("labels", []) if lbl["name"] in 
type_labels),
+        "unknown",
+    )
+
+
+def _build_row_for_marker(
+    marker: RunMarker,
+    issue: dict[str, Any],
+    issue_labels: set[str],
+) -> dict[str, Any]:
+    """Build a single analytics row from one marker."""
+    status = _resolve_status(marker, issue_labels)
+    pr_number = marker.pr_number
+    pr_url = marker.pr_url
+    pr_created_at: str | None = None
+    merged_at: str | None = None
+    review_outcome: str | None = None
+
+    if pr_number:
+        pr_details = _get_pr_details(pr_number)
+        if pr_details:
+            pr_created_at = pr_details.get("created_at")
+            merged_at = pr_details.get("merged_at")
+            if pr_details.get("merged"):
+                status = "merged"
+        reviews = _get_pr_reviews(pr_number)
+        for review in reversed(reviews):
+            state = review.get("state", "")
+            if state in (
+                "APPROVED",
+                "CHANGES_REQUESTED",
+                "DISMISSED",
+            ):
+                review_outcome = state.lower()
+                break
+        if review_outcome is None and status == "merged":
+            review_outcome = "approved"
+
+    checks: list[dict[str, str]] = []
+    if pr_number:
+        checks = _get_pr_checks(pr_number)
+
+    triggered_at = marker.created_at
+    time_to_pr = _seconds_between(_parse_iso(triggered_at), 
_parse_iso(pr_created_at))
+    time_to_merge = _seconds_between(_parse_iso(triggered_at), 
_parse_iso(merged_at))
+
+    return {
+        "run_id": marker.run_id,
+        "issue_number": issue["number"],
+        "issue_type": _get_issue_type(issue),
+        "issue_title": issue.get("title", ""),
+        "issue_url": issue["html_url"],
+        "triggered_by": marker.triggered_by,
+        "status": status,
+        "devin_session_url": marker.devin_session_url,
+        "pr_number": pr_number,
+        "pr_url": pr_url,
+        "acceptance_criteria_source": (marker.acceptance_criteria_source),
+        "reviewer_login": marker.reviewer_login,
+        "review_requested": 1 if marker.review_requested else 0,
+        "checks_total": len(checks),
+        "checks_passed": sum(1 for c in checks if c["conclusion"] == 
"success"),
+        "checks_failed": sum(1 for c in checks if c["conclusion"] == 
"failure"),
+        "ci_result": _classify_ci(checks),
+        "review_outcome": review_outcome,
+        "merged": 1 if status == "merged" else 0,
+        "return_reason": (
+            marker.review_request_error
+            if marker.status == "returned_to_human"
+            else None
+        ),
+        "triggered_at": triggered_at,
+        "pr_created_at": pr_created_at,
+        "merged_at": merged_at,
+        "reverted_at": None,
+        "time_to_pr_seconds": time_to_pr,
+        "time_to_merge_seconds": time_to_merge,
+    }
+
+
+_STATUS_PRIORITY: dict[str, int] = {
+    "merged": 0,
+    "reverted": 1,
+    "waiting_review": 2,
+    "in_progress": 3,
+    "returned_to_human": 4,
+}
+
+
+def _pick_best_row(rows: list[dict[str, Any]]) -> dict[str, Any]:
+    """Pick the most representative row for an issue.
+
+    Prefers merged > reverted > waiting_review > in_progress > 
returned_to_human.
+    """
+    return min(
+        rows,
+        key=lambda r: (
+            _STATUS_PRIORITY.get(r["status"], 99),
+            0 if r.get("pr_number") else 1,
+        ),
+    )
+
+
+def build_run_rows(issues: list[dict[str, Any]]) -> list[dict[str, Any]]:
+    """Build analytics rows from issue metadata.
+
+    Returns one row per issue, picking the most representative run.
+    """
+    rows: list[dict[str, Any]] = []
+
+    for issue in issues:
+        issue_number = issue["number"]
+        markers = _get_all_markers_for_issue(issue_number)
+        if not markers:
+            continue
+
+        issue_labels = {lbl["name"] for lbl in issue.get("labels", [])}
+
+        candidate_rows = [
+            _build_row_for_marker(marker, issue, issue_labels) for marker in 
markers
+        ]
+        rows.append(_pick_best_row(candidate_rows))
+
+    return rows
+
+
+def write_sqlite(rows: list[dict[str, Any]], output_path: pathlib.Path) -> 
None:
+    """Write rows to a SQLite database."""
+    schema_path = pathlib.Path(__file__).parent / "schema.sql"
+    schema_sql = schema_path.read_text()
+
+    db_path = output_path / "automation_runs.sqlite"
+    if db_path.exists():
+        db_path.unlink()
+
+    conn = sqlite3.connect(str(db_path))
+    cursor = conn.cursor()
+    cursor.executescript(schema_sql)
+
+    columns = [
+        "run_id",
+        "issue_number",
+        "issue_type",
+        "issue_title",
+        "issue_url",
+        "triggered_by",
+        "status",
+        "devin_session_url",
+        "pr_number",
+        "pr_url",
+        "acceptance_criteria_source",
+        "reviewer_login",
+        "review_requested",
+        "checks_total",
+        "checks_passed",
+        "checks_failed",
+        "review_outcome",
+        "return_reason",
+        "triggered_at",
+        "pr_created_at",
+        "merged_at",
+        "reverted_at",
+        "time_to_pr_seconds",
+        "time_to_merge_seconds",
+    ]
+
+    placeholders = ", ".join(["?"] * len(columns))
+    col_names = ", ".join(columns)
+
+    # Columns are hardcoded above, not from user input
+    stmt = "INSERT OR REPLACE INTO automation_runs (%s) VALUES (%s)"  # noqa: 
S608
+    stmt = stmt % (col_names, placeholders)
+    for row in rows:
+        values = [row.get(col) for col in columns]
+        cursor.execute(stmt, values)
+
+    conn.commit()
+    conn.close()
+    print(f"SQLite database written to {db_path}")
+
+
+def write_csv(rows: list[dict[str, Any]], output_path: pathlib.Path) -> None:
+    """Write rows to a CSV file."""
+    csv_path = output_path / "automation_runs.csv"
+    if not rows:
+        csv_path.write_text("")
+        return
+
+    fieldnames = list(rows[0].keys())
+    with open(csv_path, "w", newline="") as f:
+        writer = csv.DictWriter(f, fieldnames=fieldnames)
+        writer.writeheader()
+        writer.writerows(rows)
+
+    print(f"CSV written to {csv_path}")
+
+
+def _compute_kpis(rows: list[dict[str, Any]]) -> dict[str, Any]:
+    """Compute summary KPIs from run rows."""
+    total = len(rows)
+    active = sum(1 for r in rows if r["status"] in ("in_progress", 
"waiting_review"))
+    merged = sum(1 for r in rows if r["status"] == "merged")
+    reverted = sum(1 for r in rows if r["status"] == "reverted")
+    returned = sum(1 for r in rows if r["status"] == "returned_to_human")
+
+    total_checks = sum(r.get("checks_total") or 0 for r in rows)
+    passed_checks = sum(r.get("checks_passed") or 0 for r in rows)
+    check_pass_rate = (
+        round(passed_checks / total_checks * 100, 1) if total_checks > 0 else 
None
+    )
+
+    with_pr = sum(1 for r in rows if r.get("pr_number"))
+    approved = sum(1 for r in rows if r.get("review_outcome") == "approved")
+    approval_rate = round(approved / with_pr * 100, 1) if with_pr > 0 else None
+
+    merge_rate = round(merged / total * 100, 1) if total > 0 else None
+    revert_rate = round(reverted / merged * 100, 1) if merged > 0 else None
+
+    pr_times = [r["time_to_pr_seconds"] for r in rows if 
r.get("time_to_pr_seconds")]
+    avg_to_pr_seconds = int(sum(pr_times) / len(pr_times)) if pr_times else 
None
+
+    merge_times = [
+        r["time_to_merge_seconds"] for r in rows if 
r.get("time_to_merge_seconds")
+    ]
+    avg_to_merge_seconds = (
+        int(sum(merge_times) / len(merge_times)) if merge_times else None
+    )
+
+    return {
+        "total": total,
+        "active": active,
+        "merged": merged,
+        "reverted": reverted,
+        "returned": returned,
+        "check_pass_rate": check_pass_rate,
+        "approval_rate": approval_rate,
+        "merge_rate": merge_rate,
+        "revert_rate": revert_rate,
+        "avg_to_pr": _format_duration(avg_to_pr_seconds),
+        "avg_to_merge": _format_duration(avg_to_merge_seconds),
+    }
+
+
+def _build_status_counts(rows: list[dict[str, Any]]) -> dict[str, int]:
+    """Count rows by status for charting."""
+    counts: dict[str, int] = {}
+    for r in rows:
+        s = r["status"]
+        counts[s] = counts.get(s, 0) + 1
+    return counts
+
+
+def write_html(rows: list[dict[str, Any]], output_path: pathlib.Path) -> None:
+    """Generate an HTML analytics report with charts and filterable table."""
+    html_path = output_path / "automation_report.html"
+    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
+    kpi = _compute_kpis(rows)
+    status_counts = _build_status_counts(rows)
+
+    # Prepare table data as JSON for JavaScript filtering
+    table_rows = []
+    for r in rows:
+        table_rows.append(
+            {
+                "issue_type": r.get("issue_type", "unknown"),
+                "issue_title": r.get("issue_title", ""),
+                "issue_url": r.get("issue_url", ""),
+                "issue_number": r.get("issue_number"),
+                "pr_url": r.get("pr_url", ""),
+                "pr_number": r.get("pr_number"),
+                "status": r.get("status", ""),
+                "ci_result": r.get("ci_result", "N/A"),
+                "review_outcome": r.get("review_outcome") or "N/A",
+                "merged": bool(r.get("merged")),
+                "triggered_at": r.get("triggered_at", ""),
+            }
+        )

Review Comment:
   **🟠 Architect Review — HIGH**
   
   The HTML report's "Devin Session" column relies on `devin_session_url`, but 
`write_html` does not include `devin_session_url` in `table_rows`, so session 
links never render and the column is always empty.
   
   **Suggestion:** Include `devin_session_url` in each `table_rows` entry (when 
present in `rows`) and keep the JavaScript `sessionLink` helper using that 
field, so the "Devin Session" column shows working links.
   
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=7b2b443d29c141a0be1fec72041a5e56&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=7b2b443d29c141a0be1fec72041a5e56&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is an **Architect / Logical Review** comment left during a code review. 
These reviews are first-class, important findings — not optional suggestions. 
Do NOT dismiss this as a 'big architectural change' just because the title says 
architect review; most of these can be resolved with a small, localized fix 
once the intent is understood.
   
   **Path:** scripts/devin_autofix/report.py
   **Line:** 494:510
   **Comment:**
        *HIGH: The HTML report's "Devin Session" column relies on 
`devin_session_url`, but `write_html` does not include `devin_session_url` in 
`table_rows`, so session links never render and the column is always empty.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   If a suggested approach is provided above, use it as the authoritative 
instruction. If no explicit code suggestion is given, you MUST still draft and 
apply your own minimal, localized fix — do not punt back with 'no suggestion 
provided, review manually'. Keep the change as small as possible: add a guard 
clause, gate on a loading state, reorder an await, wrap in a conditional, etc. 
Do not refactor surrounding code or expand scope beyond the finding.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>



##########
scripts/devin_autofix/monitor_sessions.py:
##########
@@ -0,0 +1,361 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Monitor active Devin autofix sessions and update GitHub state.
+
+Polls the Devin API for each active run, detects PRs, comments PR links
+on issues, requests reviewers, and updates labels/markers.
+
+Usage:
+  python -m scripts.devin_autofix.monitor_sessions
+"""
+
+from __future__ import annotations
+
+import os
+from typing import Any
+
+import requests
+
+from scripts.devin_autofix.github_state import (
+    ACTIVE_STATUSES,
+    add_label,
+    get_issue_comments,
+    get_issues_with_label,
+    GITHUB_API,
+    parse_marker,
+    post_comment,
+    remove_label,
+    REPO,
+    RunMarker,
+)
+from scripts.devin_autofix.reviewers import request_review
+
+DEVIN_API = "https://api.devin.ai/v3";
+
+TERMINAL_STATUSES = frozenset({"exit", "error", "suspended"})
+TERMINAL_DETAILS = frozenset(
+    {
+        "finished",
+        "inactivity",
+        "user_request",
+        "usage_limit_exceeded",
+    }
+)
+
+
+def _devin_headers() -> dict[str, str]:
+    api_key = os.environ["DEVIN_API_KEY"]
+    return {
+        "Authorization": f"Bearer {api_key}",
+        "Content-Type": "application/json",
+    }
+
+
+def _github_headers() -> dict[str, str]:
+    token = os.environ["GITHUB_TOKEN"]
+    return {
+        "Authorization": f"token {token}",
+        "Accept": "application/vnd.github+json",
+        "X-GitHub-Api-Version": "2022-11-28",
+    }
+
+
+def get_devin_session(session_id: str) -> dict[str, Any]:
+    """Fetch a Devin session by ID."""
+    org_id = os.environ["DEVIN_ORG_ID"]
+    devin_id = session_id if session_id.startswith("devin-") else 
f"devin-{session_id}"
+    resp = requests.get(
+        f"{DEVIN_API}/organizations/{org_id}/sessions/{devin_id}",
+        headers=_devin_headers(),
+        timeout=30,
+    )
+    resp.raise_for_status()
+    result: dict[str, Any] = resp.json()
+    return result
+
+
+def get_pr_checks(pr_number: int) -> list[dict[str, str]]:
+    """Fetch check runs for a PR's head commit."""
+    # Get PR details for head SHA
+    resp = requests.get(
+        f"{GITHUB_API}/repos/{REPO}/pulls/{pr_number}",
+        headers=_github_headers(),
+        timeout=30,
+    )
+    resp.raise_for_status()
+    pr = resp.json()
+    head_sha = pr["head"]["sha"]
+
+    # Fetch check runs
+    checks_resp = requests.get(
+        f"{GITHUB_API}/repos/{REPO}/commits/{head_sha}/check-runs",
+        headers=_github_headers(),
+        params={"per_page": "100"},
+        timeout=30,
+    )
+    checks_resp.raise_for_status()
+    check_runs = checks_resp.json().get("check_runs", [])
+
+    results: list[dict[str, str]] = []
+    for cr in check_runs:
+        results.append(
+            {
+                "name": cr["name"],
+                "conclusion": cr.get("conclusion") or cr.get("status", 
"pending"),
+            }
+        )
+    return results
+
+
+def is_pr_merged(pr_number: int) -> bool:
+    """Return True if the pull request has been merged."""
+    resp = requests.get(
+        f"{GITHUB_API}/repos/{REPO}/pulls/{pr_number}",
+        headers=_github_headers(),
+        timeout=30,
+    )
+    resp.raise_for_status()
+    pr: dict[str, Any] = resp.json()
+    merged: bool = pr.get("merged", False)
+    return merged
+
+
+def _find_issues_with_active_runs() -> list[int]:
+    """Find issue numbers that have active autofix labels.
+
+    Scans issues in **all** states (open and closed) so that runs whose
+    PR merge auto-closed the issue are still finalized.
+    """
+    issue_numbers: set[int] = set()
+
+    for label in ["devin-autofix/in-progress", "devin-autofix/waiting-review"]:
+        issues = get_issues_with_label(label, state="all")
+        for issue in issues:
+            # Skip pull requests (they also appear in issue search)
+            if "pull_request" not in issue:
+                issue_numbers.add(issue["number"])
+
+    return sorted(issue_numbers)
+
+
+def _get_latest_marker(issue_number: int) -> tuple[RunMarker | None, int | 
None]:
+    """Get the latest active marker and its comment ID for an issue."""
+    comments = get_issue_comments(issue_number)
+    latest_marker: RunMarker | None = None
+    latest_comment_id: int | None = None
+
+    for comment in reversed(comments):
+        marker = parse_marker(comment.get("body", ""))
+        if marker and marker.status in ACTIVE_STATUSES:
+            latest_marker = marker
+            latest_comment_id = comment["id"]
+            break
+
+    return latest_marker, latest_comment_id
+
+
+def _handle_returned_to_human(
+    issue_number: int,
+    marker: RunMarker,
+    session: dict[str, Any],
+) -> None:
+    """Mark a run as returned-to-human when Devin finishes without a PR."""
+    structured = session.get("structured_output")
+    return_reason = "Devin session ended without opening a PR."
+    if structured and isinstance(structured, dict):
+        return_reason = structured.get("return_reason") or return_reason
+
+    marker.status = "returned_to_human"
+    body = (
+        f"⚠️ **Devin Autofix** — returned to human\n\n"
+        f"- **Reason:** {return_reason}\n"
+        f"- **Session:** [link]({marker.devin_session_url})\n\n"
+        f"{marker.to_marker()}"
+    )
+    post_comment(issue_number, body)
+    remove_label(issue_number, "devin-autofix/in-progress")
+    add_label(issue_number, "devin-autofix/returned-human")
+    print(f"  Run {marker.run_id} returned to human: {return_reason}")
+
+
+def _handle_complete(
+    issue_number: int,
+    marker: RunMarker,
+) -> None:
+    """Mark a run as complete when its PR has been merged."""
+    marker.status = "complete"
+    body = (
+        f"✅ **Devin Autofix** — complete\n\n"
+        f"- **PR:** [#{marker.pr_number}]({marker.pr_url}) has been merged.\n"
+        f"- **Session:** [link]({marker.devin_session_url})\n\n"
+        f"{marker.to_marker()}"
+    )
+    post_comment(issue_number, body)
+    remove_label(issue_number, "devin-autofix/in-progress")
+    remove_label(issue_number, "devin-autofix/waiting-review")
+    add_label(issue_number, "devin-autofix/complete")
+    print(f"  Run {marker.run_id} complete (PR #{marker.pr_number} merged)")
+
+
+def _handle_new_pr(
+    issue_number: int,
+    marker: RunMarker,
+    pull_requests: list[dict[str, Any]],
+) -> None:
+    """Link a newly detected PR to the issue and request review."""
+    pr_info = pull_requests[0]
+    pr_url = pr_info.get("pr_url") or pr_info.get("url", "")
+    pr_number = pr_info.get("number")
+
+    if not pr_number and pr_url:
+        parts = pr_url.rstrip("/").split("/")
+        try:
+            pr_number = int(parts[-1])
+        except (ValueError, IndexError):
+            pass
+
+    if not pr_number:
+        return
+
+    marker.pr_number = pr_number
+    marker.pr_url = pr_url
+    marker.pr_link_commented = True
+
+    pr_body = (
+        f"🔗 **Devin Autofix** — PR opened\n\n"
+        f"- **Pull Request:** [#{pr_number}]({pr_url})\n"
+        f"- **Session:** [link]({marker.devin_session_url})\n\n"
+        f"{marker.to_marker()}"
+    )
+    post_comment(issue_number, pr_body)
+
+    review_result = request_review(
+        pr_number=pr_number,
+        triggered_by=marker.triggered_by,
+    )
+    marker.review_requested = review_result["review_requested"]
+    marker.reviewer_login = review_result.get("reviewer_login")
+    if review_result.get("error"):
+        marker.review_request_error = review_result["error"]
+
+    try:
+        marker.checks = get_pr_checks(pr_number)
+    except Exception as exc:
+        print(f"  Error fetching checks for PR #{pr_number}: {exc}")
+
+    remove_label(issue_number, "devin-autofix/in-progress")
+    add_label(issue_number, "devin-autofix/waiting-review")
+    marker.status = "waiting_review"
+
+    print(
+        f"  PR #{pr_number} linked to issue #{issue_number}, "
+        f"review requested: {marker.review_requested}"
+    )
+
+
+def process_run(issue_number: int, marker: RunMarker) -> None:
+    """Process a single active autofix run."""
+    if not marker.devin_session_id:
+        print(f"  No session ID for run {marker.run_id}, skipping")
+        return
+
+    try:
+        session = get_devin_session(marker.devin_session_id)
+    except requests.HTTPError as exc:
+        print(f"  Error fetching session {marker.devin_session_id}: {exc}")
+        return
+
+    session_status = session.get("status", "")
+    status_detail = session.get("status_detail", "")
+    pull_requests = session.get("pull_requests", [])
+
+    print(
+        f"  Session {marker.devin_session_id}: "
+        f"status={session_status}, detail={status_detail}, "
+        f"PRs={len(pull_requests)}"
+    )
+
+    # Resolve the PR number from the marker or the Devin session.
+    pr_number = marker.pr_number
+    pr_url = marker.pr_url
+    if not pr_number and pull_requests:
+        pr_info = pull_requests[0]
+        pr_url = pr_info.get("pr_url") or pr_info.get("url", "")
+        pr_number = pr_info.get("number")
+        if not pr_number and pr_url:
+            parts = pr_url.rstrip("/").split("/")
+            try:
+                pr_number = int(parts[-1])
+            except (ValueError, IndexError):
+                pass
+
+    # Check for a merged PR first — this can happen from any active status
+    # (e.g. if the PR was opened and merged between monitor polls).
+    if pr_number:
+        try:
+            if is_pr_merged(pr_number):
+                marker.pr_number = pr_number
+                marker.pr_url = pr_url
+                _handle_complete(issue_number, marker)
+                return
+        except Exception as exc:
+            print(f"  Error checking merge status for PR #{pr_number}: {exc}")
+
+    is_terminal = session_status in TERMINAL_STATUSES
+    if is_terminal and not pull_requests and marker.status == "in_progress":
+        _handle_returned_to_human(issue_number, marker, session)
+        return
+
+    if pull_requests and not marker.pr_link_commented:
+        _handle_new_pr(issue_number, marker, pull_requests)
+    elif marker.pr_number and marker.status == "waiting_review":
+        try:
+            checks = get_pr_checks(marker.pr_number)
+            marker.checks = checks
+
+            failed = [c for c in checks if c["conclusion"] == "failure"]
+            if failed:
+                check_names = ", ".join(c["name"] for c in failed[:5])
+                check_body = (
+                    f"⚠️ **Devin Autofix** — some checks failed\n\n"
+                    f"- **Failed checks:** {check_names}\n"
+                    f"- **PR:** [#{marker.pr_number}]({marker.pr_url})\n\n"
+                    f"{marker.to_marker()}"
+                )
+                post_comment(issue_number, check_body)
+        except Exception as exc:

Review Comment:
   **🟠 Architect Review — HIGH**
   
   When a PR remains in `waiting_review` with failing checks, each monitor poll 
posts a new "some checks failed" issue comment, causing repeated, identical 
failure notifications that can spam and bury more meaningful updates.
   
   **Suggestion:** Make failed-check notifications idempotent by recording that 
failures have already been reported (for example in the marker) and only 
posting a comment on the first transition to failing or when the set of failing 
checks changes.
   
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=b14cf919286c459584052fc7a732be4e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=b14cf919286c459584052fc7a732be4e&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is an **Architect / Logical Review** comment left during a code review. 
These reviews are first-class, important findings — not optional suggestions. 
Do NOT dismiss this as a 'big architectural change' just because the title says 
architect review; most of these can be resolved with a small, localized fix 
once the intent is understood.
   
   **Path:** scripts/devin_autofix/monitor_sessions.py
   **Line:** 326:340
   **Comment:**
        *HIGH: When a PR remains in `waiting_review` with failing checks, each 
monitor poll posts a new "some checks failed" issue comment, causing repeated, 
identical failure notifications that can spam and bury more meaningful updates.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   If a suggested approach is provided above, use it as the authoritative 
instruction. If no explicit code suggestion is given, you MUST still draft and 
apply your own minimal, localized fix — do not punt back with 'no suggestion 
provided, review manually'. Keep the change as small as possible: add a guard 
clause, gate on a loading state, reorder an await, wrap in a conditional, etc. 
Do not refactor surrounding code or expand scope beyond the finding.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to