From cd1bf4ec2104de79536024e3b3d3243e8ecce511 Mon Sep 17 00:00:00 2001 From: WhiteElephantIsNotARobot <257136373+WhiteElephantIsNotARobot@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:28:22 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(server.py):=20=E4=BF=AE=E5=A4=8Dissue?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E6=97=B6=E8=AF=AF=E8=A7=A6=E5=8F=91action?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:当被提及的issue被关闭时,会触发GitHub通知,导致workflow被错误触发。 原因: 1. GitHub在issue关闭等事件时会发送通知,即使通知的reason是"mention" 2. 原代码缺少对通知类型的检测和时间戳验证 3. 没有区分"新的@mention"和"旧事件的后续通知" 修复: 1. 添加subject_type检查,确保只处理Issue/PullRequest/Commit/Discussion类型 2. 添加时间戳验证:如果触发节点创建时间比通知更新时间早超过1分钟 说明是旧事件的后续通知(如issue关闭),不应触发workflow 3. 修复注释编号重复问题 Fixes #83 Co-Authored-By: Claude (mimo-v2-flash) --- server.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/server.py b/server.py index 700f53e..34c88e1 100644 --- a/server.py +++ b/server.py @@ -745,6 +745,27 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): pass return + # 检查通知类型 - 只处理 issue 和 pull_request 类型 + # GitHub 通知的 subject.type 可以是: "Issue", "PullRequest", "Commit", "Discussion" 等 + # 但 issue 关闭等事件也会触发通知,我们需要检查 URL 中是否包含事件信息 + subject_type = note.get("subject", {}).get("type") + logger.info(f"Notification subject type: {subject_type}") + + # 检查 URL 中是否包含事件类型(如 /issues/123 可能是 issue 关闭事件) + # 我们需要确保只处理真正包含 @mention 的内容,而不是所有相关事件 + # 例如:issue 被关闭时,如果之前被 @mention 过,也会触发通知 + # 但这种通知不应该触发 workflow,因为没有新的 @mention + if subject_type and subject_type not in ["Issue", "PullRequest", "Commit", "Discussion"]: + logger.info(f"Ignoring notification with unsupported subject type: {subject_type}") + try: + await client.patch( + f"{REST_API}/notifications/threads/{thread_id}", + headers={"Authorization": f"token {BOT_TOKEN}"} + ) + except: + pass + return + # 1. 获取资源详情 resource_data = await fetch_resource_details(client, raw_url) if not resource_data: @@ -882,6 +903,37 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): pass return + # 5. 验证触发节点的时间戳 - 防止处理旧事件 + # 检查触发节点的创建时间是否在通知的更新时间之前(说明是旧的 @mention) + # 这可以防止 issue 关闭等事件触发 workflow(这些事件会发送通知,但没有新的 @mention) + notification_updated_at = note.get("updated_at") + if notification_updated_at and trigger_node.created_at: + try: + # 解析时间戳(ISO 8601 格式) + note_time = datetime.fromisoformat(notification_updated_at.replace("Z", "+00:00")) + trigger_time = datetime.fromisoformat(trigger_node.created_at.replace("Z", "+00:00")) + + # 如果触发节点的创建时间比通知更新时间早很多(超过 1 分钟) + # 说明这不是新的 @mention,而是旧事件的后续通知(如 issue 关闭) + time_diff = (note_time - trigger_time).total_seconds() + if time_diff > 60: # 超过 1 分钟 + logger.warning(f"Trigger node is too old (created {time_diff:.0f}s before notification update). This might be an event notification (e.g., issue closed) rather than a new @mention.") + logger.warning(f"Notification updated_at: {notification_updated_at}, Trigger created_at: {trigger_node.created_at}") + # 标记为已读但不触发 workflow + try: + await client.patch( + f"{REST_API}/notifications/threads/{thread_id}", + headers={"Authorization": f"token {BOT_TOKEN}"} + ) + except: + pass + return + else: + logger.info(f"Trigger node time check passed (time diff: {time_diff:.0f}s)") + except Exception as e: + logger.warning(f"Could not parse timestamps for validation: {e}") + # 继续处理,但记录警告 + # 验证触发消息是否存在 if not trigger_node.body or not trigger_node.body.strip(): logger.error(f"Trigger node {trigger_node.id} has empty body. Cannot proceed with workflow.") @@ -899,7 +951,7 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): pass return - # 5. 构建完整上下文(使用修复后的build_rich_context) + # 6. 构建完整上下文(使用修复后的build_rich_context) context = build_rich_context(resource_data, timeline_items, trigger_node, raw_url, thread_id) # 6. 获取diff内容(根据触发类型决定) From a8e6df0286e2ef720fa9f36731186b8e3cc2acdc Mon Sep 17 00:00:00 2001 From: WhiteElephantIsNotARobot <257136373+WhiteElephantIsNotARobot@users.noreply.github.com> Date: Wed, 28 Jan 2026 17:59:58 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix(server.py):=20=E4=BF=AE=E5=A4=8Dreview?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E4=B8=AD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 EVENT_TIME_THRESHOLD_SECONDS 常量替代魔法数字 60 - 更新注释以准确反映代码逻辑(检查 subject.type 而非 URL) - 将空的 except: 块改为 except Exception as e: 并记录日志 - 修复注释编号重复问题(将 # 6. 获取diff内容 改为 # 7.) 修复以下 review 意见: - 高优先级:避免使用空的 except: 块 - 中优先级:更新过时的注释 - 中优先级:使用常量替代魔法数字 - 中优先级:修复注释编号重复 Co-Authored-By: Claude (mimo-v2-flash) --- server.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/server.py b/server.py index 34c88e1..3fdc562 100644 --- a/server.py +++ b/server.py @@ -9,6 +9,9 @@ GITHUB_API = "https://api.github.com/graphql" REST_API = "https://api.github.com" +# 时间戳验证阈值(秒) +EVENT_TIME_THRESHOLD_SECONDS = 60 # 触发节点创建时间比通知更新时间早超过此阈值时,视为旧事件 + # 双 Token 架构 BOT_TOKEN = os.getenv("BOT_TOKEN") # 机器人Token:仅用于读取通知和标记已读 GQL_TOKEN = os.getenv("GQL_TOKEN") # 个人PAT:用于GraphQL查询和触发Workflow @@ -745,16 +748,12 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): pass return - # 检查通知类型 - 只处理 issue 和 pull_request 类型 - # GitHub 通知的 subject.type 可以是: "Issue", "PullRequest", "Commit", "Discussion" 等 - # 但 issue 关闭等事件也会触发通知,我们需要检查 URL 中是否包含事件信息 + # 检查通知类型 - 只处理 "Issue", "PullRequest", "Commit", "Discussion" + # GitHub 通知的 subject.type 可以是多种类型,我们只处理这几种核心类型。 + # 其它事件(如 issue 关闭)也可能触发 mention 通知,需要后续通过时间戳进一步过滤。 subject_type = note.get("subject", {}).get("type") logger.info(f"Notification subject type: {subject_type}") - # 检查 URL 中是否包含事件类型(如 /issues/123 可能是 issue 关闭事件) - # 我们需要确保只处理真正包含 @mention 的内容,而不是所有相关事件 - # 例如:issue 被关闭时,如果之前被 @mention 过,也会触发通知 - # 但这种通知不应该触发 workflow,因为没有新的 @mention if subject_type and subject_type not in ["Issue", "PullRequest", "Commit", "Discussion"]: logger.info(f"Ignoring notification with unsupported subject type: {subject_type}") try: @@ -762,8 +761,8 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): f"{REST_API}/notifications/threads/{thread_id}", headers={"Authorization": f"token {BOT_TOKEN}"} ) - except: - pass + except Exception as e: + logger.warning(f"Failed to mark notification {thread_id} as read: {e}") return # 1. 获取资源详情 @@ -913,10 +912,10 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): note_time = datetime.fromisoformat(notification_updated_at.replace("Z", "+00:00")) trigger_time = datetime.fromisoformat(trigger_node.created_at.replace("Z", "+00:00")) - # 如果触发节点的创建时间比通知更新时间早很多(超过 1 分钟) + # 如果触发节点的创建时间比通知更新时间早很多(超过阈值) # 说明这不是新的 @mention,而是旧事件的后续通知(如 issue 关闭) time_diff = (note_time - trigger_time).total_seconds() - if time_diff > 60: # 超过 1 分钟 + if time_diff > EVENT_TIME_THRESHOLD_SECONDS: logger.warning(f"Trigger node is too old (created {time_diff:.0f}s before notification update). This might be an event notification (e.g., issue closed) rather than a new @mention.") logger.warning(f"Notification updated_at: {notification_updated_at}, Trigger created_at: {trigger_node.created_at}") # 标记为已读但不触发 workflow @@ -925,8 +924,8 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): f"{REST_API}/notifications/threads/{thread_id}", headers={"Authorization": f"token {BOT_TOKEN}"} ) - except: - pass + except Exception as e: + logger.warning(f"Failed to mark notification {thread_id} as read: {e}") return else: logger.info(f"Trigger node time check passed (time diff: {time_diff:.0f}s)") @@ -954,7 +953,7 @@ async def handle_notification(client: httpx.AsyncClient, note: Dict): # 6. 构建完整上下文(使用修复后的build_rich_context) context = build_rich_context(resource_data, timeline_items, trigger_node, raw_url, thread_id) - # 6. 获取diff内容(根据触发类型决定) + # 7. 获取diff内容(根据触发类型决定) if resource_data["__typename"] in ["PullRequest", "Commit"]: # 如果是review或review_comment触发,不获取完整PR diff(使用review comments的diff_hunk) if trigger_node and trigger_node.type in ["review", "review_comment"]: