Skip to content

Preserve exception chains in AsgiFastStream startup#2781

Merged
Lancetnik merged 1 commit intoag2ai:mainfrom
zoni:fix-2780-AsgiFastStream-destroys-exception-chains-raised-in-on_startup
Feb 26, 2026
Merged

Preserve exception chains in AsgiFastStream startup#2781
Lancetnik merged 1 commit intoag2ai:mainfrom
zoni:fix-2780-AsgiFastStream-destroys-exception-chains-raised-in-on_startup

Conversation

@zoni
Copy link
Contributor

@zoni zoni commented Feb 25, 2026

Description

raise ex from None in AsgiFastStream's start_lifespan_context was unconditionally clearing ex.__cause__, destroying any exception chain deliberately set with raise B from A in an on_startup hook.

This is now changed to raise ex from ex.__cause__ so that intentional chains are preserved while the noisy ExceptionGroup wrapper is still suppressed, and a testcase has been added to prevent future regressions.

Fixes #2780

Type of change

  • Bug fix (a non-breaking change that resolves an issue)

Checklist

  • My code adheres to the style guidelines of this project (just lint shows no errors)
  • I have conducted a self-review of my own code
  • I have made the necessary changes to the documentation
  • My changes do not generate any new warnings
  • I have added tests to validate the effectiveness of my fix or the functionality of my new feature
  • Both new and existing unit tests pass successfully on my local environment by running just test-coverage
  • I have ensured that static analysis tests are passing by running just static-analysis
  • I have included code examples to illustrate the modifications

@zoni zoni requested a review from Sehat1137 as a code owner February 25, 2026 16:55
@CLAassistant
Copy link

CLAassistant commented Feb 25, 2026

CLA assistant check
All committers have signed the CLA.

@zoni
Copy link
Contributor Author

zoni commented Feb 25, 2026

just test succeeds but just lint fails. The former run via docker while the latter runs natively, and uv sync has some dependency issues:

➜  just lint
just _linter ruff format
uv run --no-dev --group lint --frozen ruff format
  × Failed to build `ruamel-yaml-clib==0.2.14`
  ├─▶ The build backend returned an error
  ╰─▶ Call to `setuptools.build_meta.build_wheel` failed (exit status: 1)

      [stdout]
      running bdist_wheel
      running build
      running build_py
      copying ./__init__.py -> build/lib.linux-x86_64-cpython-314/ruamel/yaml/clib
      copying ./setup.py -> build/lib.linux-x86_64-cpython-314/ruamel/yaml/clib
      copying ./LICENSE -> build/lib.linux-x86_64-cpython-314/ruamel/yaml/clib
      running build_ext
      building '_ruamel_yaml' extension
      gcc -fno-strict-overflow -Wsign-compare -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -fexceptions -fcf-protection -fexceptions -fcf-protection
      -fexceptions -fcf-protection -O3 -fPIC -I/home/work/.cache/uv/builds-v0/.tmpw8jVbC/include -I/usr/include/python3.14 -c _ruamel_yaml.c -o
      build/temp.linux-x86_64-cpython-314/_ruamel_yaml.o
      exiting tmpfile {'full_package_name': 'ruamel.yaml.clib', 'version_info': (0, 2, 14), '__version__': '0.2.14', 'version_timestamp': '2025-09-22
      18:47:49', 'author': 'Anthon van der Neut', 'author_email': 'a.van.der.neut@ruamel.eu', 'description': 'C version of reader, parser and emitter
      for ruamel.yaml derived from libyaml', 'license': 'MIT', 'entry_points': None, 'nested': True, 'binary_only': True, 'since': 2019, 'ext_modules':
      [{'name': '_ruamel_yaml', 'src': ['_ruamel_yaml.c', 'api.c', 'writer.c', 'dumper.c', 'loader.c', 'reader.c', 'scanner.c', 'parser.c',
      'emitter.c'], 'lib': [], 'test': '\n            int main(int argc, char* argv[])\n            {\n              /* prevent warning */\n
      return 0;\n            }\n            '}], 'classifiers': ['Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software
      Development :: Libraries :: Python Modules'], 'keywords': 'yaml 1.2 parser c-library config', 'wheels': {'windows': 'appveyor', 'linux':
      'libyaml-devel', 'macos': 'builder@macos'}, 'url_doc': 'https://yaml.dev/doc/{full_package_name}/', 'supported': [(3, 9)], 'python_requires':
      '>=3.9', 'tox': {'env': '*'}, 'manifest': 'include README.md LICENSE setup.py *.c *.h *.pxd *.pyx'}

      [stderr]
      /home/work/.cache/uv/builds-v0/.tmpw8jVbC/lib64/python3.14/site-packages/wheel/bdist_wheel.py:4: FutureWarning: The 'wheel' package is no longer
      the canonical location of the 'bdist_wheel' command, and will be removed in a future release. Please update to setuptools v70.1 or later which
      contains an integrated version of this command.
        warn(
      /home/work/.cache/uv/builds-v0/.tmpw8jVbC/lib64/python3.14/site-packages/setuptools/dist.py:765: SetuptoolsDeprecationWarning: License
      classifiers are deprecated.
      !!

              ********************************************************************************
              Please consider removing the following classifiers in favor of a SPDX license expression:

              License :: OSI Approved :: MIT License

              See https://packaging.python.org/en/latest/guides/writing-pyproject-toml/#license for details.
              ********************************************************************************

      !!
        self._finalize_license_expression()
      _ruamel_yaml.c:16:10: fatal error: Python.h: No such file or directory
         16 | #include "Python.h"
            |          ^~~~~~~~~~
      compilation terminated.
      error: command '/usr/bin/gcc' failed with exit code 1

      hint: This error likely indicates that you need to install a library that provides "Python.h" for `ruamel-yaml-clib@0.2.14`
  help: `ruamel-yaml-clib` (v0.2.14) was included because `faststream:lint` (v0.6.6) depends on `semgrep` (v1.150.0) which depends on
        `ruamel-yaml-clib`
error: Recipe `_linter` failed on line 86 with exit code 1
error: Recipe `ruff-format` failed on line 91 with exit code 1

Considering I'm 99% sure linter will be happy, I'm going to be a little bit lazy and just rely on your CI checks to show linters are happy too, or go down the rabbithole of sorting out the installation issues only when PR checks turn out to fail 😄

(For the record, my global install of ruff is happy too, so the likelihood of version differences with the version declared in pyproject.toml is probably pretty small)

@zoni
Copy link
Contributor Author

zoni commented Feb 25, 2026

For completeness, the results before and after this change using the minimal reproduction case from #2780

Before

$ uv run uvicorn repro:app
INFO:     Started server process [469048]
INFO:     Waiting for application startup.
2026-02-25 18:05:00,981 INFO     - FastStream app starting...
ERROR:    Traceback (most recent call last):
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 281, in lifespan
    async with self.start_lifespan_context():
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib64/python3.14/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 247, in start_lifespan_context
    raise ex from None
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 237, in start_lifespan_context
    await tg.start(self.__start, logging.INFO, run_extra_options)
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/anyio/_backends/_asyncio.py", line 917, in start
    return await future
           ^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 262, in __start
    self._start_hooks_context(**run_extra_options),
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/_internal/application.py", line 224, in _start_hooks_context
    await func(**run_extra_options)
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/use.py", line 170, in injected_wrapper
    return await real_model.asolve(  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
    )
    ^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/core/model.py", line 339, in asolve
    response = await run_async(self.call, *final_args, **final_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/utils.py", line 47, in run_async
    return await cast(Callable[P, Awaitable[T]], func)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/faststream-chained-exception-bug/repro.py", line 30, in startup
    raise RuntimeError("application startup failed") from e
RuntimeError: application startup failed

ERROR:    Application startup failed. Exiting.

After

$ uv run uvicorn repro:app
INFO:     Started server process [468433]
INFO:     Waiting for application startup.
2026-02-25 18:04:34,569 INFO     - FastStream app starting...
ERROR:    Traceback (most recent call last):
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/faststream-chained-exception-bug/repro.py", line 28, in startup
    raise ConnectionError("root cause: could not reach service on port 3306")
ConnectionError: root cause: could not reach service on port 3306

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 281, in lifespan
    async with self.start_lifespan_context():
               ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib64/python3.14/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 247, in start_lifespan_context
    raise ex from ex.__cause__
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 237, in start_lifespan_context
    await tg.start(self.__start, logging.INFO, run_extra_options)
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/anyio/_backends/_asyncio.py", line 917, in start
    return await future
           ^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/asgi/app.py", line 262, in __start
    self._start_hooks_context(**run_extra_options),
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib64/python3.14/contextlib.py", line 214, in __aenter__
    return await anext(self.gen)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/faststream/_internal/application.py", line 224, in _start_hooks_context
    await func(**run_extra_options)
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/use.py", line 170, in injected_wrapper
    return await real_model.asolve(  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    ...<5 lines>...
    )
    ^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/core/model.py", line 339, in asolve
    response = await run_async(self.call, *final_args, **final_kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/.venv/lib64/python3.14/site-packages/fast_depends/utils.py", line 47, in run_async
    return await cast(Callable[P, Awaitable[T]], func)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/work/Workspace/github/castoredc/reporting-event-processor/faststream-chained-exception-bug/repro.py", line 30, in startup
    raise RuntimeError("application startup failed") from e
RuntimeError: application startup failed

ERROR:    Application startup failed. Exiting.

@zoni zoni force-pushed the fix-2780-AsgiFastStream-destroys-exception-chains-raised-in-on_startup branch from 493753c to 54a72b3 Compare February 25, 2026 17:09
Lancetnik
Lancetnik previously approved these changes Feb 25, 2026
Copy link
Member

@Lancetnik Lancetnik left a comment

Choose a reason for hiding this comment

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

Thank you a lot for the fix!

@Lancetnik
Copy link
Member

Considering I'm 99% sure linter will be happy, I'm going to be a little bit lazy and just rely on your CI checks to show linters are happy too, or go down the rabbithole of sorting out the installation issues only when PR checks turn out to fail 😄

Sorry, seems like the linter is not happy 😢

`raise ex from None` in AsgiFastStream's `start_lifespan_context` was
unconditionally clearing `ex.__cause__`, destroying any exception chain
deliberately set with `raise B from A` in an `on_startup` hook.

This is now changed to `raise ex from ex.__cause__` so that intentional
chains are preserved while the noisy ExceptionGroup wrapper is still
suppressed, and a testcase has been added to prevent future regressions.

Fixes ag2ai#2780
@zoni zoni force-pushed the fix-2780-AsgiFastStream-destroys-exception-chains-raised-in-on_startup branch from 54a72b3 to 349b3f9 Compare February 26, 2026 09:54
@zoni
Copy link
Contributor Author

zoni commented Feb 26, 2026

Sorry, seems like the linter is not happy 😢

Well, that's what I get for trying to be lazy. Instant karma 😂 Apologies for the needless extra back-and-forth.

Resolving the dependency installation issues proved to be pretty straightforward in the end. Both just lint and just test-all are happy now, so I believe this should be good to go 🤞

@Lancetnik Lancetnik enabled auto-merge February 26, 2026 16:09
@Lancetnik Lancetnik added this pull request to the merge queue Feb 26, 2026
Merged via the queue into ag2ai:main with commit 8cd1e6d Feb 26, 2026
28 of 48 checks passed
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: AsgiFastStream destroys exception chains raised in on_startup hooks

3 participants