{"record":{"id":"3ea239ac24286ab5","repo":"chroma-core/chroma","slug":"groupby-keys-cannot-be-empty","errorCode":null,"errorMessage":"GroupBy keys cannot be empty","messagePattern":"GroupBy keys cannot be empty","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/execution/expression/groupBy.ts","lineNumber":116,"sourceCode":"      },\n    };\n  }\n}\n\nexport interface GroupByJSON {\n  keys: string[];\n  aggregate: AggregateJSON;\n}\n\nexport type GroupByInput = GroupBy | GroupByJSON;\n\nexport class GroupBy {\n  constructor(\n    public readonly keys: Key[],\n    public readonly aggregate: Aggregate,\n  ) {\n    if (keys.length === 0) {\n      throw new Error(\"GroupBy keys cannot be empty\");\n    }\n  }\n\n  public static from(input: GroupByInput | undefined): GroupBy | undefined {\n    if (input === undefined || input === null) {\n      return undefined;\n    }\n    if (input instanceof GroupBy) {\n      return input;\n    }\n    if (isPlainObject(input)) {\n      const data = input as GroupByJSON;\n      if (!data.keys || !Array.isArray(data.keys)) {\n        throw new TypeError(\"GroupBy requires 'keys' array\");\n      }\n      if (!data.aggregate) {\n        throw new TypeError(\"GroupBy requires 'aggregate'\");\n      }","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/execution/expression/groupBy.ts#L98-L134","documentation":"GroupBy requires at least one grouping key (the metadata field(s) to group records by, e.g. 'category'). The constructor rejects an empty keys array with a plain Error, because a group-by over no fields is meaningless and would silently produce a wrong query.","triggerScenarios":"new GroupBy([], Aggregate.minK([\"score\"], 5)); or GroupBy.from({ keys: [], aggregate: { $min_k: { keys: [\"score\"], k: 5 } } }) — note Aggregate.from succeeds first, then the GroupBy constructor throws.","commonSituations":"Building the group-by key list dynamically from user-selected facets and receiving an empty selection. Copying the aggregate's keys into the group-by keys position (both are arrays named 'keys' in the JSON shape, an easy swap).","solutions":["Provide at least one metadata field to group by: GroupBy.from({ keys: [\"category\"], aggregate })","Validate the facet/field selection is non-empty before building the GroupBy, and return a domain-level error to the caller","Double-check you did not swap the outer group-by keys with the inner aggregate keys"],"exampleFix":"// before\nconst gb = GroupBy.from({\n  keys: facets.filter((f) => f.enabled), // empty selection -> Error\n  aggregate: Aggregate.minK([\"score\"], 5),\n});\n\n// after\nconst enabled = facets.filter((f) => f.enabled);\nif (enabled.length === 0) throw new Error(\"Select at least one facet to group by\");\nconst gb = GroupBy.from({ keys: enabled, aggregate: Aggregate.minK([\"score\"], 5) });","handlingStrategy":"validation","validationCode":"const groupKeys = facets.filter((f) => f.enabled).map((f) => f.name);\nif (groupKeys.length === 0) {\n  throw new Error(\"Select at least one field to group by\");\n}\nconst gb = GroupBy.from({ keys: groupKeys, aggregate: Aggregate.minK([\"score\"], 5) });","typeGuard":"const hasGroupKeys = (keys: unknown): keys is string[] =>\n  Array.isArray(keys) && keys.length > 0 && keys.every((k) => typeof k === \"string\");","tryCatchPattern":null,"preventionTips":["Validate facet selections before building GroupBy","Keep group-by keys and aggregate keys in distinctly named variables — both are arrays named 'keys' in JSON","Return a user-facing error for empty facet selections instead of letting the client throw"],"tags":["chroma","group-by","validation"],"backgroundTag":"empty-array-argument","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}