From c9209e4adc4cb8a762020352801474cec37ede9f Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sat, 12 Sep 2026 11:52:52 +0800 Subject: [PATCH] =?UTF-8?q?fix(deploy):=20=E5=A4=B1=E8=B4=A5=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=B8=8D=E5=86=8D=E6=8A=8A=E6=89=80=E6=9C=89=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E9=83=BD=E8=AF=B4=E6=88=90=E3=80=8CGateway=20?= =?UTF-8?q?=E4=B8=8D=E5=8F=AF=E8=BE=BE=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # 误导的来源 `reportFailure` 只在 `result.sent` 上分叉,而 sent 为假同时覆盖「fetch 抛」「HTTP 4xx/5xx」, 于是两种情况都打印同一句话。 # 实测代价 切换插件时 dsh 进了崩溃循环(我自己的部署缺陷所致),通知脚本连打六条 「Gateway 不可达」并写进 spool —— 而网关**一直在正常服务**(NRestarts=0), 日志里真实响应是 **HTTP 403**: POST http://127.0.0.1:8180/api/v1/mail/send ... - 403 245B 403 的原因是同一会话连续中继邮件撞上防「两个 Agent 互相唤醒」的跳数上限 (`maxRelayHops=5`):告警邮件的会话别名由主题派生,崩溃循环下六封落进同一条会话。 一句话把人送去查网络,而问题在策略层 —— **故障通知本身给出误导性诊断, 是「静默失败」的另一种形态**。 # 修法 新增 `describeSendError`:`Gateway HTTP : ` 归类为「网关可达,但返回 HTTP xxx(附响应体)」;只有 fetch 本身失败 / AbortError 才说「不可达」/「超时」。 双向验证(真实走代码路径,都不实际发信): 网关指向关闭端口 → 「网关不可达:fetch failed」 真网关 + 坏密钥 → 「网关可达,但返回 HTTP 401:{"error":"密钥无效"}」 --- deploy/service-failure-notify.mjs | 42 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/deploy/service-failure-notify.mjs b/deploy/service-failure-notify.mjs index ea186aa..bded842 100644 --- a/deploy/service-failure-notify.mjs +++ b/deploy/service-failure-notify.mjs @@ -213,6 +213,26 @@ export async function flushSpool(env = process.env) { return { total: names.length, sent, failed }; } +/** + * 把发送失败翻译成一句能定位原因的话。 + * + * `postPayload` 对非 2xx 抛的是 `Gateway HTTP : ` —— 里面已经带着 + * 状态码与响应体,直接打出来就够定位了。只有 fetch 本身抛(DNS/连接/超时)才真的是 + * 「不可达」,此时按名字区分开。 + */ +function describeSendError(error) { + const message = String(error?.message || error || ''); + const httpMatch = message.match(/^Gateway HTTP (\d{3}): ([\s\S]*)$/); + if (httpMatch) { + return `网关可达,但返回 HTTP ${httpMatch[1]}:${httpMatch[2].slice(0, 300)}`; + } + if (error?.name === 'AbortError') return '请求超时(网关未在限定时间内响应)'; + if (/fetch failed|ECONNREFUSED|ENOTFOUND|EHOSTUNREACH/i.test(message)) { + return `网关不可达:${message}`; + } + return message || '未知错误'; +} + async function main(argv = process.argv.slice(2), env = process.env) { const serviceName = argValue(argv, '--service'); if (argv.includes('--flush')) { @@ -234,9 +254,25 @@ async function main(argv = process.argv.slice(2), env = process.env) { env, serviceName, }); - console.error(result.sent - ? `[agentmail-failure-notify] ${serviceName} 异常已上报` - : `[agentmail-failure-notify] Gateway 不可达,报告已暂存 ${result.path}`); + if (result.sent) { + console.error(`[agentmail-failure-notify] ${serviceName} 异常已上报`); + return; + } + /* + * 区分「连不上」与「连上了但被拒」。 + * + * 原先两种情况都打印「Gateway 不可达」。实测代价:切换插件时 dsh 进了崩溃循环, + * 通知脚本连打六条「Gateway 不可达」并写进 spool —— 而网关**一直在正常服务** + * (NRestarts=0),真实响应是 **HTTP 403**(同一会话连续中继邮件撞上防互相唤醒 + * 的跳数上限,`maxRelayHops=5`)。 + * + * 那句话会把人送去查网络,而问题在策略层。故障通知本身给出误导性诊断, + * 是「静默失败」的另一种形态。 + */ + console.error( + `[agentmail-failure-notify] ${serviceName} 异常上报失败,报告已暂存 ${result.path}` + ); + console.error(`[agentmail-failure-notify] 原因:${describeSendError(result.error)}`); } const isCLI = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);