{"record":{"id":"733f0a42d584da51","repo":"chroma-core/chroma","slug":"groupby-requires-aggregate","errorCode":null,"errorMessage":"GroupBy requires 'aggregate'","messagePattern":"GroupBy requires 'aggregate'","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"clients/new-js/packages/chromadb/src/execution/expression/groupBy.ts","lineNumber":133,"sourceCode":"    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      }\n      return new GroupBy(\n        data.keys.map((k) => new Key(k)),\n        Aggregate.from(data.aggregate),\n      );\n    }\n    throw new TypeError(\n      \"GroupBy input must be a GroupBy instance or plain object\",\n    );\n  }\n\n  public toJSON(): GroupByJSON {\n    return {\n      keys: this.keys.map((key) => key.name),\n      aggregate: this.aggregate.toJSON(),\n    };\n  }\n}","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/clients/new-js/packages/chromadb/src/execution/expression/groupBy.ts#L115-L151","documentation":"When GroupBy.from() receives a plain object, it requires a truthy 'aggregate' property alongside keys. Omitting aggregate — or setting it to null/0/empty — raises this TypeError, because a group-by without an aggregation has nothing to compute per group.","triggerScenarios":"GroupBy.from({ keys: [\"category\"] }) — aggregate omitted entirely. GroupBy.from({ keys: [\"category\"], aggregate: null }) — null is falsy and rejected.","commonSituations":"Building the group-by JSON incrementally and forgetting to attach the aggregate. Conditional code that only sets aggregate when a option flag is on, leaving it undefined otherwise. Confusing the group-by keys requirement with the aggregate requirement after refactoring.","solutions":["Always pair keys with an aggregate: GroupBy.from({ keys: [\"category\"], aggregate: Aggregate.minK([\"score\"], 5) })","If the aggregate is conditionally built, default it (e.g. minK over 'score' with k=5)","Use the typed Aggregate helpers so the aggregate is never a falsy half-constructed value"],"exampleFix":"// before\nGroupBy.from({ keys: [\"category\"], ...(withStats && { aggregate: Aggregate.minK([\"score\"], 5) }) });\n\n// after\nGroupBy.from({\n  keys: [\"category\"],\n  aggregate: withStats ? Aggregate.minK([\"score\"], 5) : Aggregate.maxK([\"score\"], 1),\n});","handlingStrategy":"validation","validationCode":"const aggregate = buildAggregate(opts) ?? Aggregate.minK([\"score\"], 5);\nconst gb = GroupBy.from({ keys: [\"category\"], aggregate });","typeGuard":"const hasAggregate = (v: unknown): v is { aggregate: unknown } =>\n  typeof v === \"object\" && v !== null && \"aggregate\" in v && (v as { aggregate: unknown }).aggregate != null;","tryCatchPattern":null,"preventionTips":["Default the aggregate when it is conditionally built","Never conditionally spread the aggregate property into the group-by object","Use Aggregate helpers so a half-built aggregate is impossible"],"tags":["chroma","group-by","json","validation"],"backgroundTag":"missing-required-field","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}