Repository navigation
fix(notify): send a Feishu webhook message at most once - #2661
Conversation
A custom-bot webhook has no idempotency field, so a POST whose answer was lost cannot be repeated: the chat shows the same message twice. feishuPost now answers feishu/no-answer for a timeout, an abort or an unreadable failure — not in notify.js's RETRYABLE, so the send gets one attempt and says the message may already be in the chat. A connection that never opened still answers network and retries to three; 429 and 5xx are unchanged. Fixes #2660
|
Verdict: PASS at 72be7c8 Ran at this tip: Not run: full Red first (hard requirement), at this tip: with the base Review points, against the recorded criteria:
Check-runs at this tip: Mergeable, design-conformant, T2 ( |
Written-by: Triage
Fixes #2660
Owner: Triage
Reviewer: Reviewer
What changed
A Feishu custom-bot webhook has no idempotency field, so a POST whose answer was lost cannot be repeated: the same message appears twice in the chat.
feishuPostused to answernetworkfor every throw, andnetworkis retryable in notify.js, so onesend()made 2–3 identical POSTs.A timeout, an abort, a reset mid-request, and any failure the code cannot read now answer
feishu/no-answer— not inRETRYABLE— so the send gets one attempt and the result names the unconfirmed delivery. A connection that never opened (ECONNREFUSED/ENOTFOUND/EAI_AGAIN) still answersnetworkand still retries to three attempts; 429 and 5xx keep their retry andRetry-After. The connect probe andconnector-sendnow say the test message / the message may already be in the chat instead of claiming Feishu refused it.notify.jschanges only its header comment: the key dedupes inside the process, and a transport that cannot dedupe is not asked to repeat a request that may already have been written.Reproduction
Pristine
e32d5a6, realfeishuTransport+ realcreateNotifier, fakefetchthat records the body and never answers (timeoutMs: 40): 3 POSTs,{ state: 'failed', error: 'network', attempts: 3, permanent: false }. At this tip the same call makes 1 POST and answers{ state: 'failed', error: 'feishu/no-answer', attempts: 1, permanent: true }.Verification at 72be7c8
Ran:
cd core && TZ=UTC DSH_HARNESS=.../d2607/repo/core/dev/harness node --import ./test/harness-resolve.mjs --test test/notify-webhook-once.test.js test/notify.test.js— red one32d5a6(3 !== 1andCould not reach Feishu), green here 16/16.test/feishu.test.js test/connectors.test.js test/feishu-channel.test.js— 71/71.node scripts/ds-bot-patches.mjs generate,check,canonical(bundled git;TMPDIRon/databecause/tmpis full) — 10 patches apply,core/equalsds-bot/+ patches, canonical green.node .github/scripts/changelog.mjs check— 17 fragments well formed.node .github/scripts/check-mojibake.mjs --check— 0 U+FFFD.Not run: full
npm run verify(told not to on this box),arch-docs(no git history here;docs/subsystems/core-host.md, which ownscore/src/host/, is updated in this PR instead), smoke test (not run; the webhook send path is covered by the unit tests).One existing assertion moved on purpose:
core/test/feishu.test.js:145expected a timed-out POST to answernetwork, which is exactly this defect; it now expectsfeishu/no-answer. No other test assertion changed.Checkpoint
{ "pr": 2661, "claim": "Triage", "issue": 2660, "summary": "`feishuPost` classified every thrown fetch as `network`, so notify.js retried a POST that may already have reached Feishu and the chat showed the message 2-3 times. A timeout, an abort, a reset mid-request or an unreadable failure now answers `feishu/no-answer`, which notify.js treats as permanent: one attempt, and the row says the message may already be in the chat. A connection that never opened still answers `network` and keeps its three attempts; 429 and 5xx are unchanged. `notify.js` changes only its header comment.", "files": [ "core/src/host/connectors.js", "core/src/host/notify.js", "core/test/notify-webhook-once.test.js", "core/test/feishu.test.js", "docs/subsystems/core-host.md", "changelog.d/notify-webhook-duplicate.fixed.md", "patches/ds-bot/0001-core-fixes.patch", "patches/ds-bot/files/src/host/notify.js", "patches/ds-bot/files/test/feishu.test.js", "patches/ds-bot/files/test/notify-webhook-once.test.js" ], "checks": { "ran": "node scripts/ds-bot-patches.mjs generate/check/canonical; node .github/scripts/changelog.mjs check; node .github/scripts/check-mojibake.mjs --check; core tests notify-webhook-once, notify, feishu, connectors, feishu-channel", "result": "green", "at": "72be7c8b166e833a6ff4e870f20f6c67eaf43c89" }, "next": "none — ready for review" }