cocoindex-io/cocoindex · error · RuntimeError
coco.use_state() cannot be called inside a memoized function
Error message
coco.use_state() cannot be called inside a memoized function
What it means
Memoized functions (@coco.fn(memo=True)) must be pure and replayable: their result depends only on inputs and code. use_state() introduces persistent component state, which breaks memoization semantics, so calling it inside a memoized function is disallowed.
Source
Thrown at python/cocoindex/_internal/api.py:867
# Typed — handle.value is Cursor, with full type inference
@dataclass
class Cursor:
pos: int
tag: str
cur = coco.use_state("cursor", type_hint=Cursor, initial_value=Cursor(0, "init"))
cur.value.pos += 1
cur.value = Cursor(cur.value.pos, "next")
"""
ctx = get_context_from_ctx()
if ctx._core_path != ctx._core_processor_ctx.stable_path:
raise RuntimeError(
"coco.use_state() cannot be called inside a `with coco.component_subpath()` block"
)
if ctx._in_memo_fn:
raise RuntimeError(
"coco.use_state() cannot be called inside a memoized function"
)
try:
# initial_value passed unserialized; engine core drops it if a value is
# already stored on the previous run for this key.
stored = ctx._core_processor_ctx.use_state(key, initial_value)
except ValueError as e:
# Rust client errors surface as ValueError; normalize to RuntimeError so
# all use_state usage errors have a consistent type for callers.
raise RuntimeError(str(e)) from None
if type_hint is not None:
deserializer = get_deserialize_fn(
type_hint, # type: ignore[arg-type] # type objects are hashable at runtime
source_label=f"use_state key {key!r}",
)
else:
deserializer = _DESERIALIZE_ANY
return StateHandle(key, stored, deserializer, ctx._core_processor_ctx)View on GitHub (pinned to e84aa99b32)
Solutions
- Remove memo=True from the function so it can hold component state
- Move the use_state() call out of the memoized function into the owning component body
- Pass the state value into the memoized function as a plain argument instead
Example fix
// before
@coco.fn(memo=True)
async def step(ctx):
cur = coco.use_state("cursor", initial_value=0)
// after
async def component_main(ctx):
cur = coco.use_state("cursor", initial_value=0)
await step(ctx, cur.value) Defensive patterns
Strategy: validation
Validate before calling
# never call coco.use_state inside @coco.fn(memo=True) functions # pass state values in as arguments instead: async def step(ctx, cursor): ...
Try / catch
try:
cur = coco.use_state("k", initial_value=0)
except RuntimeError as e:
if "memoized function" in str(e):
raise # move state to the owning component and pass it in Prevention
- Keep memo=True functions pure: no state, no I/O side effects on component state
- Hoist use_state to the component body and pass values into memoized helpers
- Review any function converted to memo=True for use_state usage
When it happens
Trigger: Calling coco.use_state(...) from within a function decorated with @coco.fn(memo=True) (detected via ctx._in_memo_fn).
Common situations: Adding state to a memoized helper when converting it into a component; copy-pasting component-body code (with use_state) into a memoized function; misunderstanding memo=True as 'cache the state too'.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- coco.use_state() cannot be called inside a `with coco.compon
- Memo state function returned an awaitable from a sync contex
- {type(self).__name__} cannot be used as a memoization key. T
- {type(obj).__name__} cannot be used as a memoization key. Th
- Unsupported type for memoization key: {type(obj)!r}. Provide
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/2e63cd380588e051.
Report an issue: GitHub.