{"record":{"id":"67c4182a59a43799","repo":"jackwener/OpenCLI","slug":"flomo-api-returned-malformed-json-err-instanceo","errorCode":null,"errorMessage":"Flomo API returned malformed JSON: ${err instanceof Error ? err.message : String(err)}","messagePattern":"Flomo API returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/flomo/memos.js","lineNumber":167,"sourceCode":"      headers: {\n        Authorization: 'Bearer ' + token,\n        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',\n        Accept: 'application/json',\n      },\n    });\n  } catch (err) {\n    throw new CommandExecutionError(`Failed to fetch Flomo memos: ${err instanceof Error ? err.message : String(err)}`);\n  }\n  if (resp.status === 401 || resp.status === 403) {\n    throw new AuthRequiredError(FLOMO_API_DOMAIN, `Flomo API returned HTTP ${resp.status}; please refresh your Flomo login session`);\n  }\n  if (!resp.ok) {\n    throw new CommandExecutionError(`Flomo API returned HTTP ${resp.status}`);\n  }\n  try {\n    return await resp.json();\n  } catch (err) {\n    throw new CommandExecutionError(`Flomo API returned malformed JSON: ${err instanceof Error ? err.message : String(err)}`);\n  }\n}\n\nasync function readAccessToken(page) {\n  const token = unwrapBrowserResult(await page.evaluate(buildGetTokenJs()));\n  if (typeof token !== 'string' || !token.trim()) {\n    throw new AuthRequiredError(FLOMO_API_DOMAIN, 'Flomo memos requires an active signed-in Flomo browser session');\n  }\n  return token.trim();\n}\n\nconst command = cli({\n  site: 'flomo',\n  name: 'memos',\n  access: 'read',\n  description: 'List your Flomo memos',\n  domain: FLOMO_API_DOMAIN,\n  strategy: Strategy.COOKIE,","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flomo/memos.js#L149-L185","documentation":"fetchFlomoJson in clis/flomo/memos.js:167 wraps resp.json() in try/catch. When the Flomo API responds with HTTP 200 (or any ok status) but the body cannot be parsed as JSON, resp.json() throws and the library rethrows it as a CommandExecutionError with the underlying parse message. This guards against proxies, WAFs, captive portals, or Flomo serving HTML/empty bodies instead of the expected JSON envelope.","triggerScenarios":"Calling the flomo memos command when the HTTPS response to https://flomoapp.com/api/v1/memo/updated/ has an ok status but a non-JSON body: an HTML login/Cloudflare challenge page, an empty body, a truncated response, or a proxy/error page returned with status 200.","commonSituations":"Corporate proxies or firewalls injecting HTML into 200 responses; VPN or captive portal intercepting the request; Cloudflare bot challenge served to the non-browser fetch(); network middleware stripping the body; Flomo API schema/CDN changes returning plain text or HTML.","solutions":["Re-run the command after checking network path (disable VPN/proxy or use a direct connection) so the real JSON response reaches fetch()","Open https://v.flomoapp.com/ in the controlled browser and re-authenticate so Cloudflare challenges are satisfied, then retry","Inspect the actual response body with curl using the same signed URL and Bearer token to see what non-JSON content is returned","If it persists, capture the inner message (from err.message) and report it — it may indicate a Flomo API format change requiring a library update"],"exampleFix":"// before (debugging raw failure)\nconst body = await fetchFlomoJson(buildSignedUrl(limit, since, slug), token);\n\n// after (capture body text for diagnosis before parsing)\nconst resp = await fetch(url, { headers: { Authorization: 'Bearer ' + token } });\nconst text = await resp.text();\nlet body;\ntry { body = JSON.parse(text); }\ncatch (err) {\n  console.error('Non-JSON response:', text.slice(0, 500));\n  throw new CommandExecutionError('Flomo API returned malformed JSON: ' + err.message);\n}","handlingStrategy":"try-catch","validationCode":"// Pre-check network reachability and content type before trusting the JSON\nconst head = await fetch('https://flomoapp.com/', { method: 'HEAD' }).catch(() => null);\nif (!head || !head.ok) {\n  throw new Error('flomoapp.com unreachable — check VPN/proxy/network before running the command');\n}","typeGuard":"function isJsonObject(v) {\n  return v !== null && typeof v === 'object' && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  await runFlomoMemos();\n} catch (err) {\n  if (err instanceof CommandExecutionError && /malformed JSON/i.test(err.message)) {\n    // non-JSON 200 body: proxy/Cloudflare interference — retry once after checking network,\n    // or re-authenticate via browser to clear bot challenges\n  } else {\n    throw err;\n  }\n}","preventionTips":["Run the command on a clean network path; disable intercepting VPNs, proxies, and captive portals","Authenticate via the managed browser first so Cloudflare/bot challenges are cleared before fetch() calls","Log the raw response body when debugging instead of only the parse error","Keep the CLI updated in case Flomo changes response formats"],"tags":["network","json-parsing","http-response","api"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}