Skip to content

SSE endpoint: run_sse does not propagate exceptions or non-2xx status codes #4244

@feng-95

Description

@feng-95

** Please make sure you read the contribution guide and file the issues in the right place. **
Contribution guide.

Describe the bug

When calling the /run_sse endpoint, exceptions raised inside event_generator (for example from runner.run_async) are caught and converted into a synthetic Event with content "Error: {e}", and the server continues to return HTTP 200 for the SSE stream instead of surfacing a non-2xx status or propagating the original exception/stacktrace to the client. This makes it hard for clients to distinguish between successful and failed runs and to debug issues based on server responses.

To Reproduce

  1. Start the ADK web server with an app/agent that will raise an exception during runner.run_async (for example, a tool or agent implementation that raises a ValueError).
  2. Use the /run_sse endpoint with streaming=True for that app/user/session.
  3. Observe that the SSE stream includes an event whose content is "Error: ...", but the HTTP status code of the response remains 200 and no original exception type or stacktrace is propagated to the client (only logged on the server side)

Expected behavior

I would expect failures during runner.run_async in the /run_sse endpoint to be surfaced more explicitly to clients, for example by:

Returning a non-2xx HTTP status code (e.g. 500) and closing the SSE stream when an unhandled exception occurs, and/or
Emitting a structured error event that includes more detailed error information (error type, message, optional stacktrace) in a consistent schema, rather than swallowing the exception and only sending a generic "Error: {e}" message.
This would make it easier for clients consuming the SSE stream to correctly handle failures and for developers to debug issues.

Additional context

Relevant code in src/google/adk/cli/adk_web_server.py:

    @app.post("/run_sse")
    async def run_agent_sse(req: RunAgentRequest) -> StreamingResponse:
        ...
        async def event_generator():
            try:
                ...
                async with Aclosing(
                    runner.run_async(...)
                ) as agen:
                    async for event in agen:
                        ...
                        yield f"data: {sse_event}\n\n"
            except Exception as e:
                logger.exception("Error in event_generator: %s", e)
                error_event = Event(
                    author="system",
                    content=types.Content(
                        role="model", parts=[types.Part(text=f"Error: {e}")]
                    ),
                )
                yield (
                    "data:"
                    f" {error_event.model_dump_json(by_alias=True, exclude_none=True)}\n\n"
                )

In this code path the original exception is logged but not propagated to the client, and the HTTP response status for the SSE stream remains 200 even when an internal error occurs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    web[Component] This issue will be transferred to adk-web

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions