Prevent Swallowed Exceptions in Python

Flags except-and-pass handlers, broad catches that return a default the caller mistakes for success, and new exceptions raised without the original cause.

What it flags

Flaggedorders/fraud.py
import logging

from orders.clients import fraud_client
from orders.models import Order

logger = logging.getLogger(__name__)

FRAUD_THRESHOLD = 0.8


def is_fraudulent(order: Order) -> bool:
    try:
        result = fraud_client.score(order_id=order.id, amount=order.total)
        return result.score > FRAUD_THRESHOLD
    except Exception as e:
        # An outage or a typo above approves every order
        logger.warning("fraud check failed: %s", e)
        return False
Passesorders/fraud.py
from orders.clients import FraudServiceUnavailable, fraud_client
from orders.models import Order

FRAUD_THRESHOLD = 0.8


class FraudCheckUnavailable(Exception):
    """The order could not be scored. Callers hold it for manual review."""


def is_fraudulent(order: Order) -> bool:
    try:
        result = fraud_client.score(order_id=order.id, amount=order.total)
    except FraudServiceUnavailable as e:
        raise FraudCheckUnavailable(f"order {order.id} could not be scored") from e
    return result.score > FRAUD_THRESHOLD

Why it matters

A handler that returns a default on any exception turns an outage, a bad deploy, or a typo into a plausible value, so the failure surfaces days later as wrong data with no error attached. Because except Exception also catches AttributeError, KeyError, and TypeError, it hides the programming mistakes that tests would otherwise expose. The code looks defensive in a diff, which is why reviewers approve it.

Agent instructions

cubic checks every pull request against these instructions. Paste them into AGENTS.md or Cursor rules and your AI coding tool follows them while it writes code.

AGENTS.md

Add this section to AGENTS.md at the repository root. Codex, Cursor, and other AI coding tools read it.

## Prevent Swallowed Exceptions in Python

Applies to `**/*.py`.

Review Python exception handling for failures that are hidden from the caller or lose their original cause.

Flag changes that:
- Catch `Exception` or `BaseException`, or use `contextlib.suppress(Exception)`, and then continue without re-raising in code whose caller needs to know the operation failed.
- Catch broadly and return a default such as `None`, `False`, `0`, `[]`, or `{}` that the caller cannot tell apart from a real result.
- Wrap a large `try` block in `except Exception`, so programming errors such as `KeyError`, `AttributeError`, or `TypeError` get the same handling as the expected failure.
- Raise a new exception inside an `except` block without `from err` (or `from None` when hiding the cause is deliberate), so the traceback reports the original as a separate failure during handling.
- Log a caught exception with only `str(e)` and then continue, which drops the traceback; use `logger.exception()` or `exc_info=True`.
- Catch `BaseException` or `asyncio.CancelledError` without re-raising, which breaks task cancellation and clean shutdown.
- Use `return`, `break`, or `continue` inside a `finally` block, which discards any exception in flight.

Do not flag:
- Handlers for a specific expected exception, such as `FileNotFoundError`, `KeyError`, or a `ValueError` from parsing, that handle it deliberately.
- Top-level boundaries such as a worker loop, request middleware, or CLI entry point that catch `Exception`, log it with the traceback, and report the failure.
- Best-effort side effects, such as metrics or cache writes, that log the failure and whose outcome the caller does not use.

Checks files matching **/*.py.

More Python agents

See all Python code review standards

Run this agent on every pull request

cubic reviews every pull request against the agents you turn on and comments on the lines that break them.

Catch this in your code