{"record":{"id":"1975f09c87ef4821","repo":"jackwener/OpenCLI","slug":"flomo-api-returned-a-malformed-memo-entry","errorCode":null,"errorMessage":"Flomo API returned a malformed memo entry","messagePattern":"Flomo API returned a malformed memo entry","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/flomo/memos.js","lineNumber":127,"sourceCode":"    .join(', ');\n}\n\nfunction normalizeImages(files) {\n  if (!Array.isArray(files)) return '';\n  return files\n    .map((file) => file?.thumbnail_url || file?.url || '')\n    .map((url) => String(url).trim())\n    .filter(Boolean)\n    .join(' | ');\n}\n\nfunction memoUrl(slug) {\n  return slug ? `https://${FLOMO_APP_DOMAIN}/mine/?memo_id=${encodeURIComponent(slug)}` : '';\n}\n\nfunction normalizeMemo(memo) {\n  if (!memo || typeof memo !== 'object' || Array.isArray(memo)) {\n    throw new CommandExecutionError('Flomo API returned a malformed memo entry');\n  }\n  const slug = String(memo.slug || memo.id || '').trim();\n  if (!slug) {\n    throw new CommandExecutionError('Flomo API returned a memo without slug/id');\n  }\n  return {\n    id: slug,\n    url: memoUrl(slug),\n    content: String(memo.content || '').trim(),\n    slug,\n    tags: normalizeTags(memo.tags),\n    images: normalizeImages(memo.files),\n    created_at: String(memo.created_at || ''),\n    updated_at: String(memo.updated_at || ''),\n  };\n}\n\nasync function fetchFlomoJson(url, token) {","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flomo/memos.js#L109-L145","documentation":"normalizeMemo validates each entry in the `data` array returned by the Flomo /memo/updated/ API before mapping it into CLI output rows. This error is thrown when an entry is not a plain object — it is null/undefined, a primitive, or an Array — meaning the upstream API response shape differs from what this CLI expects. It indicates an API contract change or an unexpected response body rather than a problem with the caller's arguments.","triggerScenarios":"Calling `flomo memos` where body.data contains a null, undefined, string, number, or array element instead of a memo object; a Flomo API schema change that alters the memo list shape; a proxy/captive portal returning an unexpected JSON structure that still parses and has code 0 with non-object data items.","commonSituations":"Flomo changing their undocumented internal API response format; a middlebox or corporate proxy injecting its own JSON body; flomoapp.com rolling out a new app version (the CLI pins app_version 4.0) that reshapes memo entries; running an outdated CLI version against the current API.","solutions":["Update the opencli package to the latest version so its Flomo parsing matches the current API schema.","Retry after a short delay — a transient bad response (e.g. proxy injecting a page) usually does not persist.","Check whether you are behind a proxy/firewall that rewrites responses to flomoapp.com and try a different network.","Inspect the raw response by calling the signed URL manually to see what the API actually returns for memo entries."],"exampleFix":"// before: CLI surfaces opaque CommandExecutionError\ntry {\n  rows = body.data.map(normalizeMemo);\n} catch (e) {\n  console.error(e.message); // 'Flomo API returned a malformed memo entry'\n}\n// after: guard entries before mapping and report the offending index\nconst bad = body.data.findIndex((m) => !m || typeof m !== 'object' || Array.isArray(m));\nif (bad !== -1) throw new Error(`memo at index ${bad} is not an object: ${JSON.stringify(body.data[bad])}`);\nrows = body.data.map(normalizeMemo);","handlingStrategy":"validation","validationCode":"function validateMemosResponse(body) {\n  if (!body || typeof body !== 'object' || body.code !== 0) return 'bad envelope';\n  if (!Array.isArray(body.data)) return 'data is not an array';\n  const bad = body.data.findIndex((m) => !m || typeof m !== 'object' || Array.isArray(m));\n  return bad === -1 ? null : `memo at index ${bad} is not an object`;\n}\n// call before mapping: const problem = validateMemosResponse(body);","typeGuard":"function isMemoEntry(m) {\n  return m !== null && typeof m === 'object' && !Array.isArray(m);\n}\nconst safeRows = body.data.filter(isMemoEntry).map(normalizeMemo);","tryCatchPattern":"try {\n  rows = body.data.map(normalizeMemo);\n} catch (err) {\n  if (err.message.includes('malformed memo entry')) {\n    console.error('Flomo response schema changed; update opencli and retry.');\n  } else { throw err; }\n}","preventionTips":["Keep the opencli package updated so parsing matches the current Flomo API schema.","Validate the response envelope (code/data shape) before mapping entries.","Log the raw response when normalization fails to ease diagnosing schema drift."],"tags":["schema-validation","api-contract","flomo","response-shape"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}