{"record":{"id":"404ef90c8c3bdd97","repo":"thedotmack/claude-mem","slug":"expected-json-array-in-observation-list-field-using-plain","errorCode":null,"errorMessage":"Expected JSON array in observation list field, using plain string fallback","messagePattern":"Expected JSON array in observation list field, using plain string fallback","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/sync/ChromaSync.ts","lineNumber":110,"sourceCode":"  created_at_epoch: number;\n  memory_session_id: string;\n  project: string;\n  platform_source: string;\n}\n\nfunction parseStringListField(\n  rawValue: string | null | undefined,\n  fieldName: 'facts' | 'concepts',\n  rowId: number,\n): string[] {\n  if (!rawValue) {\n    return [];\n  }\n\n  try {\n    const parsed = JSON.parse(rawValue);\n    if (!Array.isArray(parsed)) {\n      logger.warn('CHROMA_SYNC', 'Expected JSON array in observation list field, using plain string fallback', {\n        fieldName,\n        rowId,\n        parsedType: typeof parsed,\n      });\n      if (typeof parsed === 'string') {\n        return parsed.trim() ? [parsed] : [];\n      }\n      return rawValue.trim() ? [rawValue] : [];\n    }\n    return parsed.filter((value): value is string => typeof value === 'string' && value.trim().length > 0);\n  } catch (error) {\n    logger.warn('CHROMA_SYNC', 'Malformed observation list field, using plain string fallback', {\n      fieldName,\n      rowId,\n      errorName: error instanceof Error ? error.name : 'NonError',\n    });\n    return rawValue.trim() ? [rawValue] : [];\n  }","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/sync/ChromaSync.ts#L92-L128","documentation":"parseStringListField expects observation list fields (facts/concepts) stored as a JSON array string. If JSON.parse succeeds but yields a non-array, the sync logs this warning and falls back to treating the value as a plain single string (or empty if blank).","triggerScenarios":"A row's facts/concepts column contains valid JSON that is not an array — e.g. '\"single fact\"' (JSON string), '{\"a\":1}' (object), or '42' — instead of '[\"a\",\"b\"]'.","commonSituations":"Older schema versions stored a plain string; a migration wrote objects; hand-edited SQLite rows; another writer serialized a single value without wrapping it in an array.","solutions":["Normalize the row's field to a JSON array string, e.g. '[\"value\"]' instead of '\"value\"'.","Identify the writer producing non-array values and fix its serialization.","Rely on the built-in plain-string fallback if the data is a single string — no action needed.","Audit affected rows with SELECT and update malformed values in place."],"exampleFix":"// before (stored value)\n\"retention: 30 days\"\n// after (stored value)\n[\"retention: 30 days\"]","handlingStrategy":"validation","validationCode":"function isJsonArrayString(s: string): boolean {\n  try { const p = JSON.parse(s); return Array.isArray(p); } catch { return false; }\n}","typeGuard":"const isStringArray = (v: unknown): v is string[] =>\n  Array.isArray(v) && v.every((x) => typeof x === 'string');","tryCatchPattern":"try { const parsed = JSON.parse(rawValue); if (!isStringArray(parsed)) { /* fallback to plain string */ } }\ncatch { /* treat rawValue as plain string */ }","preventionTips":["Always serialize list fields with JSON.stringify(array)","Write a migration asserting list columns hold JSON arrays","Add a CHECK/constraint or writer test for the serialized shape"],"tags":["json","schema","sync","fallback"],"backgroundTag":"unexpected-response-shape","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}