jackwener/OpenCLI · error · AuthRequiredError

${message}

Error message

${message}

What it means

When the parsed Flomo envelope has body.code !== 0, the command in clis/flomo/memos.js:207 builds a message from body.message (or a fallback with the code). If isAuthFailureMessage() detects auth-related keywords (auth, login, token, permission, 登录, etc.), it throws AuthRequiredError; otherwise the server's own message is rethrown as a CommandExecutionError. This propagates Flomo's application-level business/API errors to the caller.

Source

Thrown at clis/flomo/memos.js:207

    { 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);
  },
});

export const __test__ = {
  buildSignedUrl,
  command,
  normalizeMemo,
  parsePositiveIntArg,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Read the surfaced message — it comes from Flomo's body.message and states the server-side reason directly
  2. If rate-limited, back off and retry after a delay; reduce polling frequency and lower --limit
  3. If the error mentions sign/params, verify the app hasn't changed the signing scheme or required query params and update buildSignedUrl accordingly
  4. Check Flomo service status / try the web app to determine whether it is a transient server-side incident

Example fix

// before (tight retry loop that keeps hitting server error codes)
for (;;) { await runMemosCommand(); }

// after (respect the server message, back off and retry with a cap)
try {
  await runMemosCommand();
} catch (err) {
  if (err instanceof CommandExecutionError && /rate|limit|freq/i.test(err.message)) {
    await new Promise((r) => setTimeout(r, 30_000));
  } else {
    throw err;
  }
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate args the server also enforces, before calling the API
const limit = Number(kwargs.limit ?? 20);
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
  throw new Error('limit must be an integer between 1 and 200');
}
if (kwargs.slug && !/^[A-Za-z0-9_-]{1,256}$/.test(kwargs.slug)) {
  throw new Error('slug must be a valid memo cursor (letters, digits, _, -)');
}

Type guard

function isApiErrorEnvelope(body) {
  return (
    body !== null && typeof body === 'object' && !Array.isArray(body) &&
    typeof body.code === 'number' && body.code !== 0
  );
}

Try / catch

try {
  await runFlomoMemos();
} catch (err) {
  if (err instanceof CommandExecutionError && /rate|limit|freq|too many/i.test(err.message)) {
    await sleep(30_000); // back off on rate-limit style server errors
    return runFlomoMemos();
  }
  if (err instanceof AuthRequiredError) {
    await refreshFlomoLogin();
    return runFlomoMemos();
  }
  throw err;
}

Prevention

When it happens

Trigger: Flomo responds with a JSON envelope whose code is nonzero — e.g. rate limiting, invalid sign/parameters, account restrictions, or server-side business errors — with a message containing no auth-related keywords.

Common situations: Exceeding Flomo API rate limits with frequent polling; sending an invalid slug pagination cursor or out-of-range limit accepted locally but rejected server-side; Flomo service incidents returning error codes; Flomo changing sign algorithm or required params (api_key/app_version) causing server rejection.

Related errors


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