fix(channels): add idempotency guard to nack() (#104903)#105009
fix(channels): add idempotency guard to nack() (#104903)#105009jiahjian wants to merge 1 commit into
Conversation
nack() lacked the idempotency guard that ack() already has. In receive pipelines that revisit completed stages, duplicate nack() calls would re-fire onNack callbacks and overwrite nackErrorMessage. Add a guard matching ack(): skip when ackState is not "pending". Fixes openclaw#104903. Co-Authored-By: Claude <[email protected]>
|
Thanks for the context here. I swept through the related work, and this is now duplicate or superseded. This PR duplicates a stronger viable fix at #104919 and broadens the change beyond the established lifecycle contract by suppressing Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. Canonical path: Land #104919 as the single canonical fix because it suppresses only completed duplicate nacks, preserves retry after callback rejection and ack-to-nack behavior, and keeps focused coverage in the existing lifecycle suite. So I’m closing this here and keeping the remaining discussion on #104919. Review detailsBest possible solution: Land #104919 as the single canonical fix because it suppresses only completed duplicate nacks, preserves retry after callback rejection and ack-to-nack behavior, and keeps focused coverage in the existing lifecycle suite. Do we have a high-confidence way to reproduce the issue? Yes. Current source deterministically repeats sequential completed Is this the best way to solve the issue? No. The narrower solution in #104919 fixes duplicate completed nacks without changing retry-after-rejection or the shipped ack-to-nack contract. Security review: Security review cleared: The three-file TypeScript patch introduces no concrete credential, permission, dependency, download, or supply-chain concern. AGENTS.md: found and applied where relevant. What I checked:
Likely related people:
Codex review notes: model internal, reasoning high; reviewed against 176647aa8457. |
|
Checking for ClawSweeper feedback |
What Problem This Solves
Fixes P2 issue #104903:
nack()increateMessageReceiveContextlacks the idempotency guard thatack()already has. In receive pipelines that revisit completed stages, duplicatenack()calls re-fire theonNackcallback and overwritenackErrorMessage— leading to repeated error handling, cleanup, or logging.Root cause:
nack()unconditionally firesonNack()whileack()correctly guards withif (ctx.ackState === "acked") { return; }. The two methods are used in the same pipeline context and should have symmetric protection.Why This Change Was Made
Added
if (ctx.ackState !== "pending") { return; }at the top ofnack(), matching the existing guard inack(). The fix is 4 lines of code insrc/channels/message/receive.ts.Evidence
Real behavior proof (
npx tsx scripts/proof-issue-104903.ts)Test results
src/channels/message/receive.test.ts— 5 regression tests:Changed files (3 files, +174/-0)
src/channels/message/receive.ts— +4 lines (idempotency guard)src/channels/message/receive.test.ts— 5 regression tests (new file)scripts/proof-issue-104903.ts— proof script (new file)🤖 Generated with Claude Code