Skip to content

fix(server): isolate push notification failures from the task lifecycle - #1329

Open
Linux2010 wants to merge 3 commits into
a2aproject:mainfrom
Linux2010:fix/1313-push-failure-guard
Open

Linux2010 wants to merge 3 commits into
a2aproject:mainfrom
Linux2010:fix/1313-push-failure-guard

Conversation

@Linux2010

@Linux2010 Linux2010 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Description

A failure in the push-notification infrastructure (a transient error while reading PushNotificationConfigStore, or a raising push_url_validator) propagated into the event-consumer's generic failure path and rewrote an otherwise successful task as TASK_STATE_FAILED, failing message/send instead of returning the task result. Root-cause analysis and a runtime reproduction are in #1313.

This adds non-breaking guards so that push delivery stays best-effort, in both handlers:

  • BasePushNotificationSender.send_notification: the get_info_for_dispatch lookup now runs inside an error boundary, and a raising push_url_validator is treated as a rejected URL (fail closed) — both log via logger.exception and degrade to "notification not sent".
  • DefaultRequestHandlerV2 (EventConsumer._update_task_state): the send_notification call is wrapped in try/except so a sender failure is logged and never disturbs the task state or the event stream.
  • LegacyRequestHandler._send_push_notification_if_needed: the same unguarded call existed in the legacy handler (both the blocking and streaming paths) — guarded identically.

This matches the existing design intent of _dispatch_notification, which already contains all delivery failures and returns bool.

Notes for reviewers:

  • Cancellation is unaffected: all guards use except Exception, and asyncio.CancelledError derives from BaseException, so cancellation still propagates.

  • The best-effort contract is now documented on the PushNotificationSender ABC and BasePushNotificationSender (custom implementations may raise; the framework logs and continues).

  • Follow the CONTRIBUTING Guide.

  • Pull Request title follows the conventional commits specification (fix: prefix for a SemVer patch).

  • Tests and linter pass.

  • Appropriate docs were updated (interface/base docstrings now state the best-effort contract).

Fixes #1313 🦕

Testing

  • tests/server/tasks/test_push_notification_sender.py:
    • config-store read failure is swallowed (no raise, no POST, logged);
    • a raising push_url_validator rejects the URL without propagating.
  • tests/server/request_handlers/test_default_request_handler_v2.py:
    • end-to-end: with a failing push config store, message/send still returns the Task with TASK_STATE_COMPLETED and the persisted state is untouched (previously rewritten to FAILED);
    • end-to-end: a raising custom PushNotificationSender (public interface) is equally harmless — covers the consumer-level guard.
  • tests/server/request_handlers/test_default_request_handler.py:
    • a raising sender does not propagate out of _send_push_notification_if_needed.
  • Lint/type: ruff check --fix, ruff format, ty check all clean (the 5 unused-ignore-comment warnings pre-exist on main).
  • Full suite: 2274 passed, 0 failed; coverage 93% (gate 88%), all touched files improved.

…le (a2aproject#1313)

A failure in the push-notification infrastructure (config store read or
URL validator) propagated into the EventConsumer's generic failure path
and rewrote an otherwise successful task as TASK_STATE_FAILED, failing
message/send instead of returning the task result.

Two non-breaking guards:

- BasePushNotificationSender.send_notification: read
  get_info_for_dispatch inside an error boundary and treat a raising
  push_url_validator as a rejected URL, both degrading to 'notification
  not sent' with an exception log.
- EventConsumer._update_task_state: wrap the send_notification call in
  try/except so a sender failure is logged and never disturbs the task
  state or the event stream.

Fixes a2aproject#1313

Signed-off-by: andy <linux2011@qq.com>
@Linux2010
Linux2010 requested a review from a team as a code owner October 9, 2026 12:14
@Iwaniukooo11 Iwaniukooo11 self-assigned this Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

Base PR Delta
src/a2a/server/agent_execution/active_task.py 95.02% 95.05% 🟢 +0.03%
src/a2a/server/cluster/database_event_stream.py 92.86% 96.94% 🟢 +4.08%
src/a2a/server/request_handlers/default_request_handler.py 97.96% 97.98% 🟢 +0.02%
src/a2a/server/tasks/base_push_notification_sender.py 94.34% 95.38% 🟢 +1.04%
Total 93.10% 93.15% 🟢 +0.05%

Generated by coverage-comment.yml

…re tests

Extends the a2aproject#1313 fix to LegacyRequestHandler: its
_send_push_notification_if_needed had the same unguarded sender call in
both the blocking and streaming paths, so a push-infrastructure failure
could fail the task there as well.

Also, per self-review:

- cover the consumer-level guard in EventConsumer._update_task_state
  with a raising custom PushNotificationSender (defense-in-depth branch
  was previously untested);
- mirror the codebase's mocked-httpx-client pattern in the v2 e2e tests;
- document the fail-closed semantics of the push URL validator and the
  defense-in-depth role of the consumer guard.

Signed-off-by: andy <linux2011@qq.com>
…senders

State the guarantee introduced by the a2aproject#1313 guards on the interface and
base implementation: implementations may raise, the framework catches
and logs, and failures never affect the task lifecycle or the event
stream.

Signed-off-by: andy <linux2011@qq.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Push notification store failure rewrites a completed task as FAILED (DefaultRequestHandlerV2)

2 participants