fix(wecom): 推送失败透出真实 errcode/errmsg + 加 diag_wecom 诊断脚本 + bump 0.26.4

之前推送失败只回 error:RuntimeError,吞了企业微信的 errcode。改成 reason=str(e)
(含 gettoken/message_send 失败的 errcode+errmsg);scripts/diag_wecom.py 分步查
gettoken vs send 的确切 errcode,服务器上直跑即可定位(可见范围/userid/凭据)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-25 10:10:56 +08:00
parent c79fc8ef0c
commit 6f7c904cca
3 changed files with 63 additions and 4 deletions

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。 # zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。 # 改版本只动这一行。
__version__ = "0.26.3" __version__ = "0.26.4"

View File

@ -190,7 +190,7 @@ def push_clawbot(
if file_path: if file_path:
client.send_file(snap.user_im_id, snap.context_token, file_path) client.send_file(snap.user_im_id, snap.context_token, file_path)
except Exception as e: # noqa: BLE001 —— 调用方据 reason 决定兜底 except Exception as e: # noqa: BLE001 —— 调用方据 reason 决定兜底
return PushResult(False, reason=f"error:{type(e).__name__}") return PushResult(False, reason=f"error: {str(e)[:200]}")
return PushResult(True, reason="sent") return PushResult(True, reason="sent")
@ -236,8 +236,8 @@ def push_wecom(user_id: UUID, text: str = "", file_path: Optional[str] = None) -
wecom.send_text(wuid, text) wecom.send_text(wuid, text)
if file_path: if file_path:
wecom.send_file(wuid, file_path) wecom.send_file(wuid, file_path)
except Exception as e: # noqa: BLE001 except Exception as e: # noqa: BLE001 —— 透出 errcode/errmsg 便于排错
return PushResult(False, channel="wecom", reason=f"error:{type(e).__name__}") return PushResult(False, channel="wecom", reason=f"error: {str(e)[:200]}")
return PushResult(True, channel="wecom", reason="sent") return PushResult(True, channel="wecom", reason="sent")

59
scripts/diag_wecom.py Normal file
View File

@ -0,0 +1,59 @@
"""企业微信推送诊断:分步查 gettoken / message_send 的确切 errcode/errmsg。
用法(服务器上,.env 同目录):
.venv/Scripts/python.exe scripts/diag_wecom.py <userid>
.env WECOM_CORPID/AGENTID/SECRETASCII 输出,secret 不打印
常见 errcode:
gettoken: 40013=corpid / 40001|42001=secret / 41002= corpid
send: 60011=无权限(应用可见范围没包含该成员)/ 81013=UserID 不存在
40056=agentid / 60020=IP 不在可信IP / 81014=该成员未关注/未激活
"""
import os
import sys
try:
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv())
except Exception:
pass
from core.wechat import wecom
def main() -> int:
uid = sys.argv[1] if len(sys.argv) > 1 else None
print("[cfg] configured:", wecom.wecom_configured())
print("[cfg] corpid:", (os.getenv("WECOM_CORPID", "") or "")[:8] + "...",
"| agentid:", os.getenv("WECOM_AGENTID", ""))
if not wecom.wecom_configured():
print("[FAIL] WECOM_CORPID/AGENTID/SECRET 没读到(确认 .env 在当前目录、值已填)")
return 1
print("[step1] gettoken ...")
try:
tok = wecom.get_access_token(force=True)
print(f"[step1] OK (token len {len(tok)})")
except Exception as e:
print(f"[step1] FAIL: {e}")
print(" → corpid 或 secret 不对(secret 必须是这个自建应用的,不是通讯录密钥)")
return 2
if not uid:
print("[step2] 跳过(没给 userid 参数);用法: diag_wecom.py <userid>")
return 0
print(f"[step2] message/send 到 userid={uid} ...")
try:
wecom.send_text(uid, "zcbot 企业微信诊断测试消息")
print(f"[step2] OK → 去企业微信查收。链路通了!")
except Exception as e:
print(f"[step2] FAIL: {e}")
print(" → 看 errcode:60011=应用可见范围没含该成员 / 81013=userid 写错"
"(大小写要和通讯录「账号」完全一致)/ 40056=agentid 错")
return 3
return 0
if __name__ == "__main__":
sys.exit(main())