Skip to content

Allow webhook retries after downstream delivery failure - #47293

Open
necoweb3 wants to merge 3 commits into
NousResearch:mainfrom
necoweb3:fix/webhook-delivery-retry-after-failure
Open

Allow webhook retries after downstream delivery failure#47293
necoweb3 wants to merge 3 commits into
NousResearch:mainfrom
necoweb3:fix/webhook-delivery-retry-after-failure

Conversation

@necoweb3

Copy link
Copy Markdown
Contributor

Summary

This changes the webhook adapter so failed deliveries do not poison the idempotency cache. deliver_only routes now cache only after a successful downstream delivery, and normal webhook runs release the delivery ID if the background agent task fails.

Why

deliver_only routes are used for push-style notifications where the webhook POST itself is the delivery. Normal webhook routes also need this behavior: if the agent task fails after accepting the event, the provider should be able to retry the same delivery ID instead of getting a false duplicate response.

Changes

  • Delay idempotency caching for deliver_only until after a successful direct delivery.
  • Release idempotency and delivery-info state if a normal webhook task fails after acceptance.
  • Keep duplicate suppression for successful deliveries.
  • Add regression tests for both failed direct delivery retries and failed agent-run retries.

Tests

python -m pytest tests/gateway/test_webhook_adapter.py tests/gateway/test_webhook_deliver_only.py -q
85 passed in ...

@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery platform/webhook Webhook / API server P2 Medium — degraded but workaround exists labels Jun 16, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for isolating the failed direct-delivery retry case. The underlying problem is present on current main, but the normal-delivery implementation needs rework before it covers the live failure path.

Problems

  • gateway/platforms/webhook.py:675 attaches to the task awaiting handle_message, but handle_message is fire-and-forget: it starts _process_message_background and returns at gateway/platforms/base.py:4802. Actual processing errors are handled in that background task at gateway/platforms/base.py:5233-5235, so this callback will normally see no exception. The existing on_processing_complete override at gateway/platforms/webhook.py:803 is the true completion seam. Commit 64ed99a6 fixed this exact lifecycle distinction.
  • Delaying the seen-ID write until after _direct_deliver() allows concurrent same-ID requests to pass the lookup and both send. Preserve an in-flight claim, removing it only after a direct-delivery failure.
  • hermes-webhook-delivery-retry-after-failure-pr.md is local PR-draft metadata and should not be tracked.

Suggested changes

  • Exercise the real adapter pipeline by stubbing _message_handler, as tests/gateway/test_webhook_session_close.py:114-135 does, and clear failed IDs from on_processing_complete.
  • Add a concurrent duplicate direct-delivery regression test.

Automated hermes-sweeper review.

Comment thread gateway/platforms/webhook.py Outdated
# Skip duplicate deliveries (webhook retries).
now = time.time()
if not self._record_delivery_id(delivery_id, now):
seen_at = self._seen_deliveries.get(delivery_id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes the in-flight idempotency claim before _direct_deliver() awaits network I/O. A second concurrent POST with this ID can also pass this lookup and send a duplicate. Keep a pending claim before the await, then remove it only on direct-delivery failure.

Comment thread gateway/platforms/webhook.py Outdated
task = asyncio.create_task(self.handle_message(event))
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)
def _forget_failed_delivery(done_task: "asyncio.Task") -> None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle_message() is fire-and-forget: it starts _process_message_background and returns before the agent run. Processing failures are caught inside that later task, so this callback will usually see exc is None. Clear failed IDs from the existing on_processing_complete hook instead, and test by stubbing _message_handler rather than handle_message.

@@ -0,0 +1,35 @@
# PR Draft: Allow webhook retries after downstream delivery failure

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this PR-draft artifact. It records local branch and review metadata rather than user-facing or developer documentation.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@necoweb3
necoweb3 force-pushed the fix/webhook-delivery-retry-after-failure branch from 4a830a0 to afdca4f Compare July 14, 2026 17:52

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Three PRs address related but distinct webhook delivery defects: #47293 releases failed delivery claims without weakening successful/concurrent deduplication, #47436 preserves home-channel thread metadata for bare platform targets, and #49591 parses inline platform:chat_id targets across both agent and deliver_only paths.

Related pull requests

  • #47293 related — (+122/-1) — merge after final re-review: The diff fixes retry poisoning by retaining the in-flight claim and removing it only on direct-delivery failure or ProcessingOutcome.FAILURE at the actual on_processing_complete seam; its concurrent-duplicate regression also preserves single-send behavior. Despite the keep_open review on #47293, the shown diff addresses the cited lifecycle and concurrency objections rather than attaching cleanup to handle_message.
  • #47436 related — (+154/-4) — merge: The diff carries home.thread_id through bare-target fallback, uses the gateway metadata builder when available, and preserves explicit deliver_extra thread precedence, with coverage for both cross-platform and deliver_only delivery. This directly addresses the keep_open review on #47436, which identifies the same current-main defect and rates the fix highly salvageable.
  • #49591 related — (+234/-10) — merge after final re-review: The diff adds a shared platform:chat_id parser used by both send() and _direct_deliver(), with inline-target precedence and regression coverage for agent and deliver_only modes. Despite the keep_open review on #49591, the updated diff shown here specifically addresses its two blockers: shared-path normalization and missing parity tests.

Suggested consolidation

Merge #47293, #47436, and #49591 as independent fixes after confirming the updated heads match the shown diffs and pass their webhook test suites. None should be closed as a duplicate: retry claim lifecycle (#47293), home-thread metadata fallback (#47436), and inline chat-target parsing (#49591) correct different causes and can coexist.

Cross-PR triage: Reviewed 3 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 30 kB of PR diffs, 5 kB of issue/PR text, 6 kB of discussion (4 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/webhook Webhook / API server sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants