{"record":{"id":"776f517488b62827","repo":"jackwener/OpenCLI","slug":"flomo-api-returned-a-memo-without-slug-id","errorCode":null,"errorMessage":"Flomo API returned a memo without slug/id","messagePattern":"Flomo API returned a memo without slug/id","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/flomo/memos.js","lineNumber":131,"sourceCode":"  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) {\n  let resp;\n  try {\n    resp = await fetch(url, {\n      headers: {","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flomo/memos.js#L113-L149","documentation":"normalizeMemo requires every memo entry to carry a usable identifier, read as `memo.slug || memo.id`. If both are missing or empty after string conversion and trim, this error is thrown because the CLI cannot build the memo's id or its URL (https://v.flomoapp.com/mine/?memo_id=...). It signals that the API returned memo objects lacking the identifier fields this CLI relies on.","triggerScenarios":"Calling `flomo memos` when a memo object in body.data has no slug and no id field (both undefined or empty strings); Flomo renaming the identifier field in their internal API; memos created by newer clients that use a different id key.","commonSituations":"Flomo app update changing the memo schema; memos synced in a partial state missing identifiers; outdated CLI version incompatible with the current undocumented API; third-party API mocks returning incomplete memo fixtures.","solutions":["Update the opencli package to the latest version to pick up schema changes for the Flomo API.","Re-authenticate / reload the Flomo session so the CLI reads fresh, complete data instead of stale partial records.","Check Flomo web UI for memos that appear corrupted or incomplete and try narrowing results with --since or --limit.","If it persists, capture the raw API response and file an issue with the offending memo shape."],"exampleFix":"// before\nnormalizeMemo({ content: 'no identifier here' }); // throws\n// after: caller-side guard so the CLI never sees an unidentifiable memo\nconst ok = body.data.filter((m) => m && typeof m === 'object' && String(m.slug || m.id || '').trim());\nconsole.warn(`skipped ${body.data.length - ok.length} memos without slug/id`);\nrows = ok.map(normalizeMemo);","handlingStrategy":"type-guard","validationCode":"const missing = body.data\n  .map((m, i) => [i, m && String(m.slug || m.id || '').trim()])\n  .filter(([, id]) => !id)\n  .map(([i]) => i);\nif (missing.length) console.warn(`memos without slug/id at indices: ${missing.join(', ')}`);","typeGuard":"function hasMemoId(memo) {\n  return memo !== null &&\n    typeof memo === 'object' && !Array.isArray(memo) &&\n    String(memo.slug || memo.id || '').trim().length > 0;\n}\nconst safeRows = body.data.filter(hasMemoId).map(normalizeMemo);","tryCatchPattern":"try {\n  rows = body.data.map(normalizeMemo);\n} catch (err) {\n  if (err.message.includes('without slug/id')) {\n    console.error('Some memos lack identifiers; filter with hasMemoId() or update opencli.');\n  } else { throw err; }\n}","preventionTips":["Filter memo entries with a slug/id type guard before calling normalizeMemo.","Keep the CLI current — identifier field names are part of an undocumented API and can change.","Re-authenticate/refresh the Flomo session if partial records appear repeatedly."],"tags":["schema-validation","missing-field","flomo","api-contract"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}