A Python exception inside a Kraus channel aborts the process instead of propagating
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 1.1k
- Forks
- 455
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 165
Description
Required prerequisites
- Consult the security policy. If reporting a security vulnerability, do not report the bug using this form. Use the process described in the policy to report the issue.
- Make sure you've read the documentation. Your issue may be addressed there.
- Search the issue tracker to verify that this hasn't already been reported. +1 or comment there if it has.
- If possible, make a PR with a failing test to give us a starting point to work on!
Describe the bug
If a Python exception is raised inside a custom Kraus channel while cudaq.apply_noise is executing, the process aborts (SIGABRT, exit 134) instead of the exception propagating to the caller. A surrounding try / except never runs.
The exception escapes as an uncaught C++ exception through the QIS C ABI boundary:
libc++abi: terminating due to uncaught exception of type nanobind::python_error
The crash report shows it unwinding out of the noise entry point:
nanobind::detail::raise_python_error() + 60
nanobind::detail::obj_vectorcall(...) + 424
__quantum__qis__apply_kraus_channel_double + 732
__quantum__qis__apply_kraus_channel_generalized::$_0::operator()<double>() const + 388
__quantum__qis__apply_kraus_channel_generalized + 108
cudaq::DefaultQPU::unifiedLaunchModule(...)
...
abort() called
The same exception raised outside a kernel is caught normally, so the object itself is fine — it is only unrecoverable when raised through the noise callback.
Steps to reproduce the bug
import cudaq
class Boom(cudaq.KrausChannel):
num_parameters = 1
num_targets = 1
def __init__(self, params):
raise ValueError("boom from python callback")
noise = cudaq.NoiseModel()
noise.register_channel(Boom)
@cudaq.kernel
def k():
q = cudaq.qvector(1)
h(q[0])
cudaq.apply_noise(Boom, 0.1, q[0])
try:
print(cudaq.sample(k, noise_model=noise))
except Exception as e:
print("caught:", type(e).__name__, e) # never reached
Result:
libc++abi: terminating due to uncaught exception of type nanobind::python_error: Traceback (most recent call last):
...
File "repro.py", line 8, in __init__
ValueError: boom from python callback
$ echo $?
134
Control — the identical error raised outside a kernel is catchable:
try:
Boom([0.1])
except ValueError as e:
print("caught normally outside kernel:", e) # prints
Expected behavior
cudaq.sample(...) should raise a catchable Python exception carrying the original traceback, as it does for other user errors. A mistake in a user-supplied channel should not terminate the interpreter.
Is this a regression? If it is, put the last known working version here.
Not known to be a regression; observed on the current release.
Environment
- CUDA-Q version: 0.15.1 (
cuda_quantum_cu13wheel, commit aca5853a76d499ecc3d5f97c2e06163ae99d9c75) - Python version: 3.13
- C++ compiler: n/a (wheel install)
- Operating system: macOS 26.5.1, arm64 (Mac16,8)
Suggested fix
__quantum__qis__apply_kraus_channel_generalized / __quantum__qis__apply_kraus_channel_double are reached across a C ABI boundary, so a C++ exception thrown from the nanobind callback has nowhere to unwind to and reaches std::terminate.
Catching nanobind::python_error (and std::exception) at the callback boundary and restoring it as a pending Python error — so it surfaces when control returns to the interpreter — would make this behave like other user errors. If preserving the traceback across that boundary is impractical, converting it to a RuntimeError naming the channel would still be a large improvement over an abort.
I'm happy to work on a fix if maintainers can confirm the preferred approach for propagating errors back across that boundary.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing __quantum__qis__apply_kraus_channel_generalized and __quantum__qis__apply_kraus_channel_double, then run the provided reproducer to observe the C ABI failure. Done means a Python exception from a custom Kraus channel reaches the caller as a catchable exception instead of aborting the process, with the traceback preserved when possible.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100