Skip to content

fix(server): run after_handler_run when a handler call is cancelled - #1427

Open
v0ropaev wants to merge 1 commit into
a2aproject:mainfrom
v0ropaev:fix/plugin-hooks-cancel
Open

v0ropaev wants to merge 1 commit into
a2aproject:mainfrom
v0ropaev:fix/plugin-hooks-cancel

Conversation

@v0ropaev

@v0ropaev v0ropaev commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to #1402, as asked in the review there.

server_plugin_hooks handled cancellation differently for unary and streaming methods, and neither told the plugin about it.

  • The unary wrapper only called after_handler_run from except Exception. A cancelled call (a client timeout, a disconnect, asyncio.wait_for around the handler) got before_handler_run and then nothing, so a plugin that opens a span or takes a slot in before_handler_run never got to close it.
  • The stream wrapper does call it from finally, but with error=None, so a cancelled stream looked the same as one that finished normally.

Both wrappers now catch asyncio.CancelledError alongside Exception, pass it to after_handler_run as error, and re-raise it. ServerResponse.error is already typed BaseException | None. A stream closed with aclose() still reports error=None, so test_stream_closed_early_still_gets_final_call is unchanged.

The two new tests use a task store whose get blocks, so they cancel inside on_get_task and on_subscribe_to_task without going through ActiveTask.subscribe and don't depend on #1402. Without the src change the unary test gets no after_handler_run call at all and the stream test gets error=None.

Full suite on 3.10: 2544 passed, 207 skipped, 4 xfailed, 1 xpassed. ./scripts/lint.sh passes (the three ty warnings it prints are the existing unused type: ignore in the client transports).

I left the client side alone. BaseClient._execute_with_interceptors has the same except Exception shape for after_client_request, and PluginAgentExecutor.execute passes None to after_execution when the execution is cancelled. I can send those separately if you want them.

The unary wrapper only called after_handler_run from except Exception,
so a cancelled call got before_handler_run with no matching after call.
The stream wrapper did call it from finally, but reported error=None,
which reads as a clean end of the stream. Both now pass the
CancelledError through to the after hook and re-raise it.
@v0ropaev
v0ropaev requested a review from a team as a code owner November 5, 2026 14:03
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

🧪 Code Coverage (vs main)

⬇️ Download Full Report

No coverage changes.

Generated by coverage-comment.yml

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.

3 participants