{"record":{"id":"3f4d72e6fc9a40ae","repo":"jackwener/OpenCLI","slug":"message-3f4d72","errorCode":null,"errorMessage":"${message}","messagePattern":"\\$\\{message\\}","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/flomo/memos.js","lineNumber":207,"sourceCode":"    { name: 'limit', type: 'int', default: 20, help: 'Number of memos to fetch (1-200)' },\n    { name: 'since', type: 'int', help: 'Only memos updated after this Unix timestamp in seconds' },\n    { name: 'slug', help: 'Pagination cursor from a previous memo page' },\n  ],\n  columns: ['id', 'url', 'content', 'slug', 'tags', 'images', 'created_at', 'updated_at'],\n  func: async (page, kwargs) => {\n    const limit = parsePositiveIntArg(kwargs.limit, 'limit', 20, MAX_LIMIT);\n    const since = parseSinceArg(kwargs.since);\n    const slug = parseSlugArg(kwargs.slug);\n    await page.wait(3).catch(() => {});\n    const token = await readAccessToken(page);\n    const body = await fetchFlomoJson(buildSignedUrl(limit, since, slug), token);\n    if (!body || typeof body !== 'object' || Array.isArray(body)) {\n      throw new CommandExecutionError('Flomo API returned a malformed response');\n    }\n    if (body.code !== 0) {\n      const message = body.message || `Flomo API error code ${body.code}`;\n      if (isAuthFailureMessage(message)) {\n        throw new AuthRequiredError(FLOMO_API_DOMAIN, message);\n      }\n      throw new CommandExecutionError(message);\n    }\n    if (!Array.isArray(body.data)) {\n      throw new CommandExecutionError('Flomo API returned malformed memo data');\n    }\n    if (body.data.length === 0) {\n      throw new EmptyResultError('flomo memos', 'No Flomo memos matched the requested filters.');\n    }\n    return body.data.map(normalizeMemo);\n  },\n});\n\nexport const __test__ = {\n  buildSignedUrl,\n  command,\n  normalizeMemo,\n  parsePositiveIntArg,","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/flomo/memos.js#L189-L225","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the surfaced message — it comes from Flomo's body.message and states the server-side reason directly","If rate-limited, back off and retry after a delay; reduce polling frequency and lower --limit","If the error mentions sign/params, verify the app hasn't changed the signing scheme or required query params and update buildSignedUrl accordingly","Check Flomo service status / try the web app to determine whether it is a transient server-side incident"],"exampleFix":"// before (tight retry loop that keeps hitting server error codes)\nfor (;;) { await runMemosCommand(); }\n\n// after (respect the server message, back off and retry with a cap)\ntry {\n  await runMemosCommand();\n} catch (err) {\n  if (err instanceof CommandExecutionError && /rate|limit|freq/i.test(err.message)) {\n    await new Promise((r) => setTimeout(r, 30_000));\n  } else {\n    throw err;\n  }\n}","handlingStrategy":"retry","validationCode":"// Validate args the server also enforces, before calling the API\nconst limit = Number(kwargs.limit ?? 20);\nif (!Number.isInteger(limit) || limit < 1 || limit > 200) {\n  throw new Error('limit must be an integer between 1 and 200');\n}\nif (kwargs.slug && !/^[A-Za-z0-9_-]{1,256}$/.test(kwargs.slug)) {\n  throw new Error('slug must be a valid memo cursor (letters, digits, _, -)');\n}","typeGuard":"function isApiErrorEnvelope(body) {\n  return (\n    body !== null && typeof body === 'object' && !Array.isArray(body) &&\n    typeof body.code === 'number' && body.code !== 0\n  );\n}","tryCatchPattern":"try {\n  await runFlomoMemos();\n} catch (err) {\n  if (err instanceof CommandExecutionError && /rate|limit|freq|too many/i.test(err.message)) {\n    await sleep(30_000); // back off on rate-limit style server errors\n    return runFlomoMemos();\n  }\n  if (err instanceof AuthRequiredError) {\n    await refreshFlomoLogin();\n    return runFlomoMemos();\n  }\n  throw err;\n}","preventionTips":["Throttle polling frequency and keep --limit within documented bounds","Only pass slug cursors exactly as returned by a previous successful page","Back off exponentially when server-side error codes appear","Monitor Flomo status for incidents; keep the CLI's sign/params up to date"],"tags":["api","server-error","error-propagation","rate-limit"],"backgroundTag":"api-error-code-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}