TencentCloud/TencentDB-Agent-Memory · error

Invalid generation log cursor

Error message

Invalid generation log cursor

What it means

MemoryGenerationLogStore.list supports opaque base64url pagination cursors encoding { beforeMs, afterLogId }. list throws "Invalid generation log cursor" when the cursor decodes to JSON lacking a finite beforeMs number or a string afterLogId, or when JSON.parse/Buffer base64url decoding itself fails and surfaces through this validation path.

Source

Thrown at MemoryCore/src/core/memory-generation-log/store.ts:146

  async getByLogId(logId: string): Promise<MemoryGenerationLog | null> {
    const match = logId.match(/^mgl_(l1|l2|l3)_(succeeded|failed)_(\d+)_([A-Za-z0-9._-]+)_([a-f0-9]+)$/);
    if (!match) return null;
    const layer = match[1] as MemoryGenerationLayer;
    const timestamp = Number(match[3]);
    const anchor = match[4];
    const { date, hour } = utcParts(timestamp);
    const key = `${ROOT}/layer=${layer}/date=${date}/hour=${hour}/${reverseTimestamp(timestamp)}__mid=${anchor}__lid=${logId}.json`;
    return this.getByKey(key);
  }

  async list(filter: MemoryGenerationLogListFilter): Promise<{ items: MemoryGenerationLogListItem[]; next_cursor?: string }> {
    const layers: MemoryGenerationLayer[] = filter.layer ? [filter.layer] : ["l1", "l2", "l3"];
    const items: MemoryGenerationLogListItem[] = [];
    let boundary: { beforeMs: number; afterLogId: string } | undefined;
    if (filter.cursor) {
      const decoded = JSON.parse(Buffer.from(filter.cursor, "base64url").toString("utf8")) as Record<string, unknown>;
      if (!Number.isFinite(decoded.beforeMs) || typeof decoded.afterLogId !== "string") {
        throw new Error("Invalid generation log cursor");
      }
      boundary = { beforeMs: Number(decoded.beforeMs), afterLogId: decoded.afterLogId };
    }
    let hourMs = Math.floor(filter.endTimeMs / 3_600_000) * 3_600_000;

    while (hourMs + 3_600_000 - 1 >= filter.startTimeMs && items.length <= filter.limit) {
      const { date, hour } = utcParts(hourMs);
      for (const layer of layers) {
        let marker: string | undefined;
        do {
          const result = await this.storage.getBackend().listObjects(
            `${ROOT}/layer=${layer}/date=${date}/hour=${hour}/`,
            { maxKeys: 1000, marker, recursive: true },
          );
          for (const entry of result.entries) {
            if (entry.isDirectory) continue;
            const parsed = parseListItem(entry.key, entry.size);
            if (!parsed || parsed.finished_at_ms < filter.startTimeMs || parsed.finished_at_ms > filter.endTimeMs) continue;

View on GitHub (pinned to 3efcd317b8)

Solutions

  1. Use a cursor exactly as returned by a previous list() response, unmodified
  2. Clear/reset the cursor and start pagination from the beginning (omit filter.cursor)
  3. If migrating from an old version, drop stored cursors — regenerate them with the current library
  4. Base64url-decode the cursor locally to inspect: JSON.parse(Buffer.from(c, "base64url")) should show { beforeMs: <number>, afterLogId: "<string>" }

Example fix

// before
await store.list({ startTimeMs: s, endTimeMs: e, limit: 100, cursor: staleCursorFromOldVersion });
// after
await store.list({ startTimeMs: s, endTimeMs: e, limit: 100 }); // restart pagination, reuse fresh cursor from response
Defensive patterns

Strategy: try-catch

Validate before calling

function isValidCursor(cursor) {
  try {
    const d = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
    return Number.isFinite(d.beforeMs) && typeof d.afterLogId === "string";
  } catch { return false; }
}

Type guard

function isDecodableCursor(cursor) {
  try {
    const d = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
    return typeof d === "object" && d !== null && Number.isFinite(d.beforeMs) && typeof d.afterLogId === "string";
  } catch { return false; }
}

Try / catch

try {
  await store.list({ ...filter, cursor });
} catch (e) {
  if (e.message === "Invalid generation log cursor") {
    logger.warn("cursor unreadable; restarting pagination from scratch");
    return store.list({ ...filter, cursor: undefined });
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling list with filter.cursor set to: a cursor produced by a different/older schema, arbitrary text (not base64url), truncated/corrupted cursor strings, or valid base64 whose JSON is missing/wrong-typed beforeMs or afterLogId.

Common situations: Cursor persisted by a client across a library upgrade where the cursor format changed; URL-encoding mangling (base64url characters altered); hand-crafted cursors in scripts; copying a cursor truncated at a shell line break.

Related errors


AI-assisted analysis of TencentCloud/TencentDB-Agent-Memory@3efcd317b8 (2026-09-01). Data as JSON: /api/errors/a43f4205da4ec5c8. Report an issue: GitHub.