jackwener/OpenCLI · error · CommandExecutionError

Flomo API returned a malformed response

Error message

Flomo API returned a malformed response

What it means

After fetchFlomoJson successfully parses a JSON body, the command in clis/flomo/memos.js:202 validates that the envelope is a non-null, non-array object before checking body.code. If the Flomo API returns JSON that is not an object (null, an array, a string, or a number), it throws CommandExecutionError('Flomo API returned a malformed response'). This indicates the API contract (an object with code/message/data) was violated.

Source

Thrown at clis/flomo/memos.js:202

  domain: FLOMO_API_DOMAIN,
  strategy: Strategy.COOKIE,
  browser: true,
  navigateBefore: `https://${FLOMO_APP_DOMAIN}/`,
  args: [
    { name: 'limit', type: 'int', default: 20, help: 'Number of memos to fetch (1-200)' },
    { name: 'since', type: 'int', help: 'Only memos updated after this Unix timestamp in seconds' },
    { name: 'slug', help: 'Pagination cursor from a previous memo page' },
  ],
  columns: ['id', 'url', 'content', 'slug', 'tags', 'images', 'created_at', 'updated_at'],
  func: async (page, kwargs) => {
    const limit = parsePositiveIntArg(kwargs.limit, 'limit', 20, MAX_LIMIT);
    const since = parseSinceArg(kwargs.since);
    const slug = parseSlugArg(kwargs.slug);
    await page.wait(3).catch(() => {});
    const token = await readAccessToken(page);
    const body = await fetchFlomoJson(buildSignedUrl(limit, since, slug), token);
    if (!body || typeof body !== 'object' || Array.isArray(body)) {
      throw new CommandExecutionError('Flomo API returned a malformed response');
    }
    if (body.code !== 0) {
      const message = body.message || `Flomo API error code ${body.code}`;
      if (isAuthFailureMessage(message)) {
        throw new AuthRequiredError(FLOMO_API_DOMAIN, message);
      }
      throw new CommandExecutionError(message);
    }
    if (!Array.isArray(body.data)) {
      throw new CommandExecutionError('Flomo API returned malformed memo data');
    }
    if (body.data.length === 0) {
      throw new EmptyResultError('flomo memos', 'No Flomo memos matched the requested filters.');
    }
    return body.data.map(normalizeMemo);
  },
});

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Retry the command — transient gateway/WAF JSON error bodies often resolve on a second attempt
  2. Dump the raw JSON response with curl using the same signed URL and Bearer token to see the unexpected shape
  3. Check for Flomo API or app version changes (the request pins app_version=4.0) and update the CLI if the API contract changed
  4. Inspect proxies/CDN in front of flomoapp.com that may substitute their own JSON responses

Example fix

// before
const body = await fetchFlomoJson(buildSignedUrl(limit, since, slug), token);
if (!body || typeof body !== 'object' || Array.isArray(body)) {
  throw new CommandExecutionError('Flomo API returned a malformed response');
}

// after (add diagnosis of the actual shape)
const body = await fetchFlomoJson(buildSignedUrl(limit, since, slug), token);
if (!body || typeof body !== 'object' || Array.isArray(body)) {
  console.error('Unexpected payload:', JSON.stringify(body)?.slice(0, 500));
  throw new CommandExecutionError('Flomo API returned a malformed response: ' + typeof body);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the envelope shape immediately after fetching, before relying on code/message
const body = await fetchFlomoJson(url, token);
if (!isJsonObject(body)) {
  console.error('Unexpected Flomo payload:', JSON.stringify(body));
  throw new Error('Flomo API envelope is not an object — check for API/schema changes');
}

Type guard

function isFlomoEnvelope(v) {
  return (
    v !== null &&
    typeof v === 'object' &&
    !Array.isArray(v) &&
    typeof v.code === 'number'
  );
}

Try / catch

try {
  await runFlomoMemos();
} catch (err) {
  if (err instanceof CommandExecutionError && /malformed response/i.test(err.message)) {
    // unexpected envelope: dump payload, retry once, then escalate/report API change
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: The Flomo memo/updated endpoint returns parseable JSON whose root is null, an array, a string, or a number instead of the expected { code, message, data } envelope — e.g. an undocumented error payload, a CDN/WAF JSON error, or an API version change.

Common situations: Flomo deploying an API schema change; an intermediary (gateway, WAF) returning its own JSON error body with status 200; hitting a different/flomoapp.com variant endpoint via DNS or proxy redirection; the account state producing an unexpected JSON payload.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/4ab1af2b1b580f31. Report an issue: GitHub.