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
- Retry the command — transient gateway/WAF JSON error bodies often resolve on a second attempt
- Dump the raw JSON response with curl using the same signed URL and Bearer token to see the unexpected shape
- Check for Flomo API or app version changes (the request pins app_version=4.0) and update the CLI if the API contract changed
- 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
- Retry transient failures before treating them as schema changes
- Capture and inspect raw payloads when the envelope shape is unexpected
- Track Flomo API/app_version changes that could alter the response contract
- Check intermediaries (CDN/WAF) that may substitute their own JSON error bodies
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
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Bilibili ${label} API returned a malformed payload
- Chess.com stats payload for ${kind} is not an object
- Chess.com stats payload for ${kind}.last is not an object
- Chess.com stats payload for ${kind}.best is not an object
- Chess.com stats payload for ${kind}.record is not an object
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/4ab1af2b1b580f31.
Report an issue: GitHub.