{"record":{"id":"ee8ee15c767e5454","repo":"makeplane/plane","slug":"and-group-must-contain-at-least-one-condition","errorCode":null,"errorMessage":"AND group must contain at least one condition","messagePattern":"AND group must contain at least one condition","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared-state/src/store/work-item-filters/adapter.ts","lineNumber":77,"sourceCode":"      }\n\n      const [property, operator, value] = conditionResult;\n      return createConditionNode({\n        property,\n        operator,\n        value,\n      });\n    }\n\n    // It's a logical group - check which type\n    const expressionKeys = Object.keys(expression);\n\n    if (LOGICAL_OPERATOR.AND in expression) {\n      const andExpression = expression as { [LOGICAL_OPERATOR.AND]: TWorkItemFilterExpressionData[] };\n      const andConditions = andExpression[LOGICAL_OPERATOR.AND];\n\n      if (!Array.isArray(andConditions) || andConditions.length === 0) {\n        throw new Error(\"AND group must contain at least one condition\");\n      }\n\n      const convertedConditions = andConditions.map((item) => this._convertExpressionToInternal(item));\n      return createAndGroupNode(convertedConditions);\n    }\n\n    throw new Error(`Invalid expression: unknown structure with keys [${expressionKeys.join(\", \")}]`);\n  }\n\n  /**\n   * Converts internal filter expression to external format\n   * @param internalFilter - The internal filter expression\n   * @returns External filter expression\n   */\n  toExternal(internalFilter: TFilterExpression<TWorkItemFilterProperty>): TWorkItemFilterExpression {\n    if (!internalFilter) {\n      return {};\n    }","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/packages/shared-state/src/store/work-item-filters/adapter.ts#L59-L95","documentation":"Thrown while converting an external filter expression shaped as an AND group: the adapter looks up expression[LOGICAL_OPERATOR.AND] and requires it to be a non-empty array. An AND key whose value is [] or not an array (string, object) is rejected because an empty logical group has no semantics in the internal TFilterExpression tree.","triggerScenarios":"Persisted payload like { AND: [] }; a UI that allows adding an AND group without children and then saves; corrupted/migrated data where the conditions array got stripped.","commonSituations":"User adds a group, removes all conditions, and the autosave writes { AND: [] }; import/migration tool that drops nested condition keys.","solutions":["Normalize on save: if an AND group has zero children, prune the group (or skip persisting it) before serialization.","On load, sanitize the payload by collapsing empty AND groups to an empty/no-filter state.","Fix the UI to prevent saving a group with no conditions."],"exampleFix":"// before\nconst saved = JSON.stringify(filterTree);\n\n// after\nfunction pruneEmptyGroups(node) {\n  if (node && typeof node === 'object' && Array.isArray(node.AND)) {\n    const kids = node.AND.map(pruneEmptyGroups).filter(Boolean);\n    return kids.length ? { AND: kids } : undefined;\n  }\n  return node;\n}\nconst saved = JSON.stringify(pruneEmptyGroups(filterTree) ?? {});","handlingStrategy":"validation","validationCode":"function normalizeAndGroup(node: any): any {\n  if (node && Array.isArray(node.AND)) {\n    const kids = node.AND.map(normalizeAndGroup).filter(Boolean);\n    if (kids.length === 0) return undefined;\n    return { AND: kids };\n  }\n  return node;\n}","typeGuard":"function isNonEmptyAndGroup(e: any): boolean {\n  return e && Array.isArray(e.AND) && e.AND.length > 0;\n}","tryCatchPattern":"try { adapter.toInternal(expr); } catch (e) {\n  if (/AND group must contain/.test((e as Error).message)) { expr = {}; /* retry as no-filter */ } else throw e;\n}","preventionTips":["Prune empty groups before persist","Block saving groups with zero conditions in the UI","Sanitize imported/migrated filter payloads"],"tags":["filters","adapter","validation","shared-state","data-integrity"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}