cocoindex-io/cocoindex · error · ValueError
resolve_pair returned matched={decision.matched!r}, which is
Error message
resolve_pair returned matched={decision.matched!r}, which is not in candidates={candidates!r}. This is a contract violation (see requirement.md). What it means
This error is raised when a user-supplied `resolve_pair` callback for entity resolution returns a `PairDecision` whose `matched` value is not one of the candidate entity names (or equals the entity itself). It enforces the library contract documented in requirement.md: the callback may only merge an entity into one of the candidates it was offered. It is an internal contract-validation guard, not a library bug.
Source
Thrown at python/cocoindex/ops/entity_resolution/__init__.py:282
def _chain_walk(dedup: dict[str, str | None], name: str) -> str:
current = name
while True:
target = dedup.get(current)
if target is None:
return current
current = target
def _validate_pair_decision(
*,
entity: str,
candidates: list[str],
decision: PairDecision,
) -> None:
if decision.matched is not None and (
decision.matched not in candidates or decision.matched == entity
):
raise ValueError(
f"resolve_pair returned matched={decision.matched!r}, "
f"which is not in candidates={candidates!r}. This is a "
f"contract violation (see requirement.md)."
)
def _apply_pair_decision(
*,
info: _EntityInfo,
decision: PairDecision,
entity_map: dict[str, _EntityInfo],
dedup: dict[str, str | None],
existing_policy: ExistingCanonicalPolicy,
) -> _DecisionApplication:
if decision.matched is None:
dedup[info.name] = None
return _DecisionApplication(canonical=info.name)
View on GitHub (pinned to e84aa99b32)
Solutions
- In your resolve_pair, only return matched values taken directly from the `candidates` argument (exact string identity), or None for no match.
- If matching an entity to a normalized/external form, map it back to the corresponding candidate string before constructing PairDecision.
- Never set matched to the entity itself; use matched=None when no candidate matches.
Example fix
// before
def resolve_pair(entity, candidates):
best = fuzzy_lookup(entity) # returns an external canonical name
return PairDecision(matched=best)
// after
def resolve_pair(entity, candidates):
best = fuzzy_lookup(entity)
if best is None or best not in candidates or best == entity:
return PairDecision(matched=None)
return PairDecision(matched=best) Defensive patterns
Strategy: validation
Validate before calling
def make_decision(entity, candidates, key):
m = key(entity, candidates)
assert m is None or (m in candidates and m != entity), f"matched={m!r} invalid for candidates={candidates!r}"
return PairDecision(matched=m) Type guard
def is_valid_matched(m, candidates):
return m is None or (isinstance(m, str) and m in candidates) Try / catch
try:
handle = await coco.use_mount(resolve_component, ...)
except ValueError as e:
if 'contract violation' in str(e):
logging.error('resolve_pair returned an invalid matched value: %s', e)
else:
raise Prevention
- Return matched values only by identity from the candidates list, never from an external dictionary.
- Return None (not the entity itself) when no candidate matches.
- Unit-test resolve_pair against synthetic candidate lists, asserting the matched-in-candidates invariant.
When it happens
Trigger: A custom `resolve_pair` function returns a `PairDecision` whose `matched` field is set to a string that is not in the `candidates` list passed to it, or sets `matched` equal to the entity being resolved (self-match), instead of `None` for no match.
Common situations: Hand-rolling a resolve_pair that fuzzy-matches against an external dictionary or LLM and returns the matched term verbatim from the dictionary rather than the candidate string; typos in candidate names; case-normalizing candidates but returning the original-cased external value; confusing 'matched' semantics and echoing the entity itself.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- {name}
- use_mount() requires a ComponentSubpath when the function ha
- LiveComponent classes cannot be used with use_mount(). Use m
- mount() requires a ComponentSubpath when the function has no
- mount_each() requires a ComponentSubpath when the function h
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/d008606f43857645.
Report an issue: GitHub.