fix(drift-card): Drift Diff HTTP 400 — item-by-item 累計長度避免切斷 HTML
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 2m0s

統帥回報 14:18 點 [查看 Diff] 收到 'Drift Diff 查詢失敗: HTTP error: 400'

真因 (telegram_gateway.py:2087 _send_drift_diff_detail):
  - report_id=7ffe78ae 有 48 items,單筆 git_value 最長 1794 字 (env array)
  - 累計 _full 遠超 4096,執行 _full[:3950] 截斷
  - 截斷可能切在 HTML tag 中間 (<code>... 或 &lt; entity 中間)
  - Telegram parse_mode='HTML' 拒絕不完整 HTML → 400

修復:
  - item-by-item 累計長度,單個 item 算 _block 長度+1
  - 預留 3800 上限 (4096 - 250 buffer 給 header + '… 還有 X 項' 提示)
  - 確保 _full 永遠是完整 HTML 結構

驗證: 下次 drift report 出現 + 統帥點 [查看 Diff] 應正常顯示 (本 session 的下個 cycle)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-19 14:26:29 +08:00
parent ddb902f1ff
commit c0f3509d39

View File

@@ -2100,26 +2100,44 @@ class TelegramGateway:
}) })
return return
_lines = [f"📊 <b>完整 Drift Diff</b> — <code>{html.escape(report_id)}</code>"] # 2026-04-19 ogt + Claude Opus 4.7: 修 HTTP 400 真因
_lines.append(f"Namespace: <code>{html.escape(_rpt.namespace)}</code>") # 原邏輯: _full[:3950] 切在 HTML tag/entity 中間 → Telegram parse_mode HTML 拒絕
_lines.append(f"HIGH×{_rpt.high_count} MEDIUM×{_rpt.medium_count} INFO×{_rpt.info_count}") # 修法: item-by-item 累計長度,超過 3800 就停,確保完整 HTML 結構
_lines.append("" * 20) # (3800 留 250 buffer 給 header + 截斷提示)
for i, _item in enumerate(_rpt.items[:50], 1): _MAX_LEN = 3800
_header = [
f"📊 <b>完整 Drift Diff</b> — <code>{html.escape(report_id)}</code>",
f"Namespace: <code>{html.escape(_rpt.namespace)}</code>",
f"HIGH×{_rpt.high_count} MEDIUM×{_rpt.medium_count} INFO×{_rpt.info_count}",
"" * 20,
]
_lines = list(_header)
_used_len = sum(len(s) + 1 for s in _header)
_shown = 0
for _item in _rpt.items:
_level = getattr(_item.drift_level, "value", str(_item.drift_level)) _level = getattr(_item.drift_level, "value", str(_item.drift_level))
_emoji = "🔴" if _level == "high" else ("🟡" if _level == "medium" else "") _emoji = "🔴" if _level == "high" else ("🟡" if _level == "medium" else "")
_field = (_item.field_path or "")[:80] _field = (_item.field_path or "")[:80]
_git = str(_item.git_value)[:40] if _item.git_value is not None else "(未設)" _git = str(_item.git_value)[:40] if _item.git_value is not None else "(未設)"
_k8s = str(_item.actual_value)[:40] if _item.actual_value is not None else "(未設)" _k8s = str(_item.actual_value)[:40] if _item.actual_value is not None else "(未設)"
_lines.append(f"{_emoji} <b>{html.escape(_field)}</b>") _block = (
_lines.append(f" Git: <code>{html.escape(_git)}</code>") f"{_emoji} <b>{html.escape(_field)}</b>\n"
_lines.append(f" K8s: <code>{html.escape(_k8s)}</code>") f" Git: <code>{html.escape(_git)}</code>\n"
if len(_rpt.items) > 50: f" K8s: <code>{html.escape(_k8s)}</code>"
_lines.append(f"… 還有 {len(_rpt.items) - 50} 項未顯示") )
if _used_len + len(_block) + 1 > _MAX_LEN:
break
_lines.append(_block)
_used_len += len(_block) + 1
_shown += 1
_remaining = len(_rpt.items) - _shown
if _remaining > 0:
_lines.append(f"… 還有 {_remaining} 項未顯示")
_full = "\n".join(_lines) _full = "\n".join(_lines)
# Telegram 訊息上限 4096 字元
if len(_full) > 4000:
_full = _full[:3950] + "\n… (截斷)"
await self._send_request("sendMessage", { await self._send_request("sendMessage", {
"chat_id": settings.OPENCLAW_TG_CHAT_ID, "chat_id": settings.OPENCLAW_TG_CHAT_ID,