ErrLookup › Background articles › AssertionError explained: why libraries abort with assert failures and what invariant broke
AssertionError explained: why libraries abort with assert failures and what invariant broke
AssertionError is the exception raised when a Python assert statement or an explicit raise AssertionError fires inside a library, test harness, or validation script. It signals that an internal invariant or contract was violated — a value out of range, misaligned data, an unsupported type, or a misconfigured environment — rather than an expected operational failure. Developers meet it everywhere from QuantConnect Lean regression algorithms and DeepSeek-V3 model construction to pytest's approx(), FastAPI routing, Dubbo reference counting, and SQLAlchemy dialect detection, and the fix is almost always to correct the condition the assert guards, not to catch or silence it.
Distilled from 181 documented records across 26 repositories.
Background
An AssertionError comes from one of two mechanisms: a bare `assert condition, message` statement, or an explicit `raise AssertionError(...)` where the author wanted an unconditional abort (pytest's Approx.__bool__, Dubbo's ReferenceCountedResource.retain(), Graal's Truffle performance-warning gate). Either way it marks a programming error or a broken assumption, not a retryable condition. Libraries choose it deliberately: Dubbo throws it on retain-after-destroy because an unbalanced refcount is "an irrecoverable programming error"; Graal treats it as a lint gate that fails compilation when configured warning kinds appear; pytest defines __bool__ to raise it so `assert approx(x)` fails loudly instead of silently passing. From the caller's side the error usually arrives with a bare message and interpolated values — the Greek values that violated a bound, the counts that didn't match, the key missing from a mapping — and no exception hierarchy to catch narrowly, which is by design: the correct response is to fix the precondition, not to handle the error.
Much of the family lives in tests and validation scripts rather than production paths. QuantConnect Lean's regression algorithms are heavily represented: they assert engine contracts like "greeks must be in valid ranges for supported option styles", "a futures subscription with extended_market_hours=False must never receive extended-hours data", and "order events for limit orders must carry a non-zero LimitPrice". MiroFish's Zep Cloud validator similarly asserts that pagination traversals match, entity-context edge counts match the graph, and updater stats show a fully drained queue. These assertions encode contracts the surrounding system must uphold; a failure means either a genuine regression in the system under test or a change in behavior the assertion (and possibly the demo relying on it) has not caught up with.
The other large cluster fires at construction or load time when the environment does not match the code's assumptions. DeepSeek-V3 asserts that vocab_size and column-parallel output features are divisible by the torch.distributed world_size before any weights load; d2l-zh asserts that centers, contexts, and negatives lists are element-aligned; stable-diffusion-webui refuses LoRA files whose keys map to unsupported module types or that no registered network module type accepts; SQLAlchemy refuses to proceed when `SELECT version()` does not parse as a PostgreSQL banner (CockroachDB, Redshift, or a pooler rewriting the string). These are fail-fast checks: they abort before partial work happens, at the cost of surprising anyone whose environment differs slightly from what the author assumed.
One quirk worth knowing: the assertion messages themselves are sometimes buggy. DeepSeek-V3's messages print `${world_size}` literally because the f-string interpolation is broken; Lean has a levshape[0]/levshape[1] copy-paste mismatch between condition and message, and one handler references a variable assigned inside the try, so the intended AssertionError surfaces as a NameError. When a message shows a placeholder instead of a value, distrust the message and read the assert condition in the source. Behavior also varies by library in strictness: pytest and FastAPI validate usage patterns of their own APIs, while Lean's checks guard engine behavior — the same exception class covers both "you used the API wrong" and "the system under test regressed".
Common causes
- Value outside expected range or allowed set.The most common trigger: a computed or passed-in value violates a bound or whitelist. Lean asserts option Greeks fall in theoretically valid ranges (CALL delta in [0,1], vega/gamma >= 0); FastAPI accepts only 'auto', 'index.html', '404.html', or None as fallback; DeepSeek-V3 rejects prompts longer than max_seq_len; Graal fails compilation when recorded performance-warning kinds intersect the configured error set.
- Environment or parallelism mismatch.The code assumes an environment shape that does not hold. DeepSeek-V3 requires vocab_size and out_features divisible by world_size, so an odd GPU count or stale RANK/MASTER_ADDR env vars from a previous torchrun trip the assert at model construction. SQLAlchemy fails when the server's version() banner is not PostgreSQL-shaped, common with wire-compatible servers or poolers that rewrite it.
- Misaligned or out-of-sync data structures.Parallel arrays or traversals that should correspond do not. d2l-zh asserts len(centers) == len(contexts) == len(negatives) for the word2vec pipeline, broken when arrays come from different corpus runs or negatives got flattened; MiroFish asserts production pagination matches a raw cursor walk and that entity-context edge counts match the graph, which can also break from concurrent mutation or indexing lag.
- Unsupported or unrecognized type, format, or annotation.A loader or decorator meets something it has no code path for. stable-diffusion-webui rejects LoRA keys mapping to unsupported module types (Conv1d, LayerNorm) or key sets no network module type accepts; d2l-zh's download_extract only handles .zip/.tar/.gz; semantic-kernel's @event rejects return annotations that are not resolvable concrete types (TypeVar, Literal, NoReturn); DeepSeek-V3's convert.py fails on checkpoint keys not in its hardcoded mapping.
- Wrong or missing configuration flags.A flag contradicts what the code then asserts. Lean regressions fail when extended_market_hours is not set as the assertion expects (extended-hours fills expected but flag off, or data arriving during hours the subscription should have excluded); Graal's TreatPerformanceWarningsAsErrors set too broadly ('all') converts warnings that previously passed into hard errors.
- Lifecycle misuse.Calling into an object past its valid lifetime. Dubbo's retain() on a ReferenceCountedResource whose count already hit zero throws AssertionError — typically an unbalanced retain/release pair or a retain racing graceful shutdown.
- API misuse patterns.Using an object in a context it explicitly forbids. pytest.approx() in a boolean context (`assert approx(x)`, `if approx(x):`) raises by design because Approx only supports == comparison; semantic-kernel handlers must be annotated `-> None`.
- Async ordering and drain assumptions.Assertions about event ordering or queue state that races invalidate. Lean demos assert self.ticket is still None when the SUBMITTED order event arrives; MiroFish asserts pending_episode_count == 0 after stop(), which fails when the queue did not fully drain or a send error was swallowed.
What usually fixes it
- Read the interpolated values in the message first — they usually name exactly which invariant broke (which Greek bound, which count mismatched, which key is missing) — then reproduce with those values. If the message shows a literal placeholder like ${world_size} instead of a number, the message itself is buggy (broken f-string); read the assert condition in the source instead.
- Fix the precondition, not the exception: assertions mark programming errors and broken contracts, so catching or disabling them (e.g. -O, clearing Graal's TreatPerformanceWarningsAsErrors, deleting a regression assert) hides the defect. Narrow the escape hatch only when the check is a confirmed false positive, and re-baseline after upgrades.
- Verify configuration and environment before the guarded call: GPU/world_size divisibility for DeepSeek model dims, explicit extended_market_hours flags, whitelisted fallback literals, and version banners for SQLAlchemy. Preflight checks that print dist.get_world_size() or validate config dims against world_size catch these before construction.
- Generate or traverse aligned data in one code path from the same inputs: regenerate centers/contexts/negatives together (d2l-zh), quiesce graphs and run validation traversals back-to-back (MiroFish), and keep mock schemas in sync with current validators rather than mixing runs.
- Update the library (or pin it) when formats and types drift: newer webui versions recognize new LoRA module types and key formats, zep-cloud SDK versions change reader behavior, and new DeepSeek checkpoint revisions rename tensor keys. Pin versions whose behavior your assertions were written against.
- For load-time format failures, validate inputs before the loader does: preview safetensors keys, diff checkpoint tensor names against the expected mapping, check file extensions and download integrity (hashes), and use dedicated dialects for PostgreSQL-compatible servers instead of the core postgresql dialect.
Documented occurrences
- Expected greeks to have valid values. Greeks were: Delta: {greeks.delta}, Rho: {greeks.rho}, Theta: {greeks.theta}, Vega: {greeks.vega}, Gamma: {greeks.gamma}(QuantConnect/Lean)
- len(centers) == len(contexts) == len(negatives)(d2l-ai/d2l-zh)
- Expected position group buying power model type: OptionStrategyPositionGroupBuyingPowerModel. Actual: {type(position_group.buying_power_model).__name__}(QuantConnect/Lean)
- Performance warning detected and is treated as a compilation error.(oracle/graal)
- Expected greeks to be calculated for {contract.symbol.value}, an {option_style_str} style option, using {type(self._option.price_model).__name__}, which supports them, but they were not(QuantConnect/Lean)
- Expected 3 option chains from history request, but got {historical_options_data_df.index.levshape[1]}(QuantConnect/Lean)
- Field self.ticket not expected no be assigned on the first order event(QuantConnect/Lean)
- Expected greeks not to be calculated for {contract.symbol.value}, an {option_style_str} style option, using {type(self._option.price_model).__name__}, which does not support them, but they were(QuantConnect/Lean)
- Algorithm should have not run on extended hours for {self._gc.symbol} future, which did not enable extended market hours(QuantConnect/Lean)
- Vocabulary size must be divisible by world size (world_size=${world_size})(deepseek-ai/DeepSeek-V3)
- This instance has been destroyed(apache/dubbo)
- Expected filtered universe to have less contracts than original universe. Filtered contracts count ({filtered_contracts}) is equal to total contracts count ({total_contracts})(QuantConnect/Lean)
- unexpected MiroFish updater stats: {updater_stats}(666ghj/MiroFish)
- MiroFish entity context omitted incoming or outgoing node edges(666ghj/MiroFish)
- Return type not found. Please use `None` as the type hint of the return type.(microsoft/semantic-kernel)
- production node pagination did not match raw cursor traversal(666ghj/MiroFish)
- Only zip/tar files can be extracted(d2l-ai/d2l-zh)
- Prompt length exceeds model maximum sequence length (max_seq_len=${model.max_seq_len})(deepseek-ai/DeepSeek-V3)
- fallback must be 'auto', 'index.html', '404.html', or None(fastapi/fastapi)
- OrderEvent LimitPrice is Not expected to be 0 for LimitOrder and StopLimitOrder(QuantConnect/Lean)
…and 161 more across the corpus — use search.
Honest provenance: generated on 2026-08-14 from AI-assisted analysis of the linked records. See how records are made.