{"record":{"id":"17e1328b19cb0924","repo":"makeplane/plane","slug":"invalid-expression-unknown-structure-with-keys","errorCode":null,"errorMessage":"Invalid expression: unknown structure with keys [${expressionKeys.join(\", \")}]","messagePattern":"Invalid expression: unknown structure with keys \\[(.+?)\\]","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared-state/src/store/work-item-filters/adapter.ts","lineNumber":84,"sourceCode":"      });\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    }\n\n    try {\n      return this._convertExpressionToExternal(internalFilter);\n    } catch (error) {\n      console.error(\"Failed to convert internal filter to external:\", error);\n      return {};\n    }","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/makeplane/plane/blob/1c8a60f858d8472aa56e29994ec1c7926da2c6ce/packages/shared-state/src/store/work-item-filters/adapter.ts#L66-L102","documentation":"Thrown at the end of _convertExpressionToInternal after the object is confirmed not to be a simple condition (no field key) and does not contain LOGICAL_OPERATOR.AND. The message lists the actual keys so the caller can see exactly what unrecognized shape was passed. Only AND groups and single conditions are supported by this adapter.","triggerScenarios":"Passing an OR group ({ OR: [...] }) which the adapter does not implement; passing { field: 'x', value: 1 } missing the keys _isWorkItemFilterConditionData expects; passing arbitrary objects like { foo: 1 } or a NOT expression.","commonSituations":"Schema/version mismatch where another part of the app (or a newer backend) emits OR/NOT groups the adapter can't parse; hand-built test fixtures with wrong keys; copy-paste of a filter payload from a different filter system.","solutions":["Inspect the keys printed in the message to see the unsupported operator/shape.","If you need OR/NOT, extend the adapter to handle LOGICAL_OPERATOR.OR/NOT, or convert the payload to AND-only form upstream.","For malformed single conditions, ensure each condition has the keys _isWorkItemFilterConditionData requires (field, operator/operator structure, value)."],"exampleFix":"// before\nadapter.toInternal({ OR: [{ field: 'state', value: 'open' }] });\n\n// after (flatten OR into an accepted shape, or extend the adapter)\nadapter.toInternal({\n  AND: [\n    { field: 'state', operator: 'is', value: 'open' },\n  ],\n});","handlingStrategy":"validation","validationCode":"const KNOWN_TOP_KEYS = new Set(['field', 'AND']);\nfunction isRecognizedShape(e: Record<string, unknown>): boolean {\n  const keys = Object.keys(e);\n  return keys.every(k => KNOWN_TOP_KEYS.has(k)) && (keys.includes('field') || keys.includes('AND'));\n}","typeGuard":"function isConditionOrAnd(e: any): boolean {\n  if (!e || typeof e !== 'object') return false;\n  return 'field' in e || ('AND' in e && Array.isArray(e.AND));\n}","tryCatchPattern":"try { adapter.toInternal(expr); } catch (e) {\n  if (/unknown structure with keys/.test((e as Error).message)) { logUnsupportedKeys(expr); expr = {}; } else throw e;\n}","preventionTips":["Confirm only AND groups and supported conditions are emitted","Extend the adapter when adding OR/NOT","Validate payloads at the API boundary before storage"],"tags":["filters","adapter","validation","shared-state","schema-mismatch"],"backgroundTag":null,"analyzedSha":"1c8a60f858d8472aa56e29994ec1c7926da2c6ce","analyzedAt":"2026-08-12T14:44:31.636Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}