chroma-core/chroma · error · ChromaUniqueError

The resource already exists

Error message

The resource already exists

What it means

_parse_k_aggregate() (operator.py:1363-1365) requires the 'keys' field inside a '$min_k'/'$max_k' aggregation to be a list or tuple; anything else raises TypeError with the offending type name. The aggregation ranks records on these keys per group (e.g. ["#score"] or ["priority", "#score"]), and _strings_to_keys (operator.py:1334-1336) iterates it element-wise — a scalar or dict cannot supply an ordered ranking key set. Unlike Select.from_dict, a set is not accepted here because multi-key aggregation order is significant.

Source

Thrown at clients/js/packages/chromadb-core/src/ChromaFetch.ts:77

        throw error;
      }
      switch (resp.status) {
        case 400:
          throw new ChromaClientError(
            `Bad request to ${input} with status: ${resp.statusText}`,
          );
        case 401:
          throw new ChromaUnauthorizedError(`Unauthorized`);
        case 403:
          throw new ChromaForbiddenError(
            `You do not have permission to access the requested resource.`,
          );
        case 404:
          throw new ChromaNotFoundError(
            `The requested resource could not be found: ${input}`,
          );
        case 409:
          throw new ChromaUniqueError("The resource already exists");
        case 422:
          if (
            respBody?.message &&
            (respBody?.message.startsWith("Quota exceeded") ||
              respBody?.message.startsWith("Billing limit exceeded"))
          ) {
            throw new ChromaQuotaExceededError(respBody?.message);
          }
          break;
        case 500:
          throw parseServerError(respBody?.error);
        case 502:
        case 503:
        case 504:
          throw new ChromaConnectionError(
            `Unable to connect to the chromadb server. Please try again later.`,
          );
      }

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Wrap keys in a list: {"$min_k": {"keys": ["#score"], "k": 3}}.
  2. If you hold a Python set from a Select, convert it: {"keys": list(select_keys)} before embedding it in an aggregate.
  3. Use '#score' (mapped to Key.SCORE by _strings_to_keys) or plain metadata field names as string elements.
  4. For multi-key ranking keep the order meaningful: ["priority", "#score"] ranks by priority first, score as tiebreaker.

Example fix

// before
aggregate = {"$min_k": {"keys": "#score", "k": 3}}   # TypeError: $min_k keys must be a list, got str

# after
aggregate = {"$min_k": {"keys": ["#score"], "k": 3}}
Defensive patterns

Strategy: type-guard

Validate before calling

def agg_keys_is_list(payload: dict) -> bool:
    op = next(iter(payload))
    body = payload.get(op, {})
    return isinstance(body, dict) and isinstance(body.get("keys"), (list, tuple))

Type guard

def is_wellformed_k_aggregate(v: Any) -> TypeGuard[Dict[str, Dict[str, Any]]]:
    if not (isinstance(v, dict) and len(v) == 1):
        return False
    op, body = next(iter(v.items()))
    return (
        op in {"$min_k", "$max_k"}
        and isinstance(body, dict)
        and isinstance(body.get("keys"), (list, tuple))
        and bool(body.get("keys"))
        and isinstance(body.get("k"), int)
        and not isinstance(body.get("k"), bool)
        and body["k"] > 0
    )

Try / catch

try:
    Aggregate.from_dict(agg)
except TypeError as e:
    if "keys must be a list" in str(e):
        op = next(iter(agg))
        ks = agg[op]["keys"]
        agg[op]["keys"] = [ks] if isinstance(ks, str) else list(ks)
    else:
        raise

Prevention

When it happens

Trigger: {"$min_k": {"keys": "#score", "k": 3}} — single string not wrapped in a list; {"keys": {"key": "#score"}} — object form from JSON config; {"keys": null}; passing a Python set — accepted by Select.from_dict but rejected here, so reusing a select payload's keys for the aggregate fails.

Common situations: Single-key aggregations written without brackets; JSON configs using an object because a config system cannot express empty/typed arrays; single-element arrays collapsed to scalars by intermediate serializers; porting a Select payload (where set/tuple are fine) into an aggregate.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/227e3f9b68f7cbb1. Report an issue: GitHub.