Desk Clarity

A field guide to everyday digital problems.

Developer tools / Practical guide

Python Contextmanager Generator Did Not Stop: Yield Once and Put Retries Outside

A generator-based context manager tries to yield another resource after an error inside the with block.

By ON Lab2 min readSources checked November 10, 2026 (KST)

A contextmanager generator represents one entry and one exit. It must yield one value; it cannot rerun the caller with block by yielding again after an exception.

Check the failure with a small fixture

A generator-based context manager tries to yield another resource after an error inside the with block.

from contextlib import contextmanager
@contextmanager
def wrong():
    try:
        yield "first"
    except ValueError:
        yield "second"
try:
    with wrong():
        raise ValueError("body failed")
except RuntimeError as exc:
    assert "generator didn't stop" in str(exc)
    print("second yield rejected")
events = []
@contextmanager
def owned_resource():
    events.append("open")
    try:
        yield "resource"
    finally:
        events.append("close")
try:
    with owned_resource():
        raise ValueError("body failed")
except ValueError:
    pass
assert events == ["open", "close"]

What the result means

The exception from the body is thrown back into the generator at its yield point. The incorrect handler yields again instead of finishing or reraising, so contextlib raises a lifecycle error. The corrected finally block performs cleanup while allowing the original ValueError to reach the caller.

Choose the correction for your caller

If an operation needs retries, put a retry loop around a fresh with statement and the operation itself. Decide whether a failed attempt can be repeated safely before doing so. If the context manager catches an exception only to log it, reraise it; otherwise the with statement may treat it as handled and continue. Keep resource acquisition before the single yield and release in finally. Return normally after yield only when the operation completed or when suppression is explicitly part of the contract.

Limits and the next check

This example uses marker strings rather than real sockets or database transactions. It does not establish that retrying an external side effect is safe. A context manager instance created from this generator is single-use; call the decorated function again to obtain a new instance.

Related: Compare a related data or execution contract.

The embedded Python example was executed as an isolated local script on November 10, 2026 (KST), using Python 3.11.6. It demonstrates the stated boundary; it is not a test of your application or a promise of compatibility with every Python release.

Sources & further reading