toeverything/AFFiNE · error · Error

Can not find the version to rollback to.

Error message

Can not find the version to rollback to.

What it means

Thrown during rollbackDoc when getDocHistory(spaceId, docId, timestamp) returns no snapshot for the requested timestamp. The rollback algorithm needs a 'from' (current) and 'to' (target) snapshot to compute a delta update; without the target it cannot generate the change. This is a plain `new Error` and surfaces as a 500 unless the resolver wraps it.

Source

Thrown at packages/backend/server/src/core/doc/storage/doc.ts:248

  abstract pushDocUpdates(
    spaceId: string,
    docId: string,
    updates: Uint8Array[],
    editorId?: string
  ): Promise<number>;

  abstract deleteDoc(spaceId: string, docId: string): Promise<void>;
  abstract deleteSpace(spaceId: string): Promise<void>;
  async rollbackDoc(
    spaceId: string,
    docId: string,
    timestamp: number,
    editorId?: string
  ): Promise<void> {
    await using _lock = await this.lockDocForUpdate(spaceId, docId);
    const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);
    if (!toSnapshot) {
      throw new Error('Can not find the version to rollback to.');
    }

    const fromSnapshot = await this.getDocSnapshot(spaceId, docId);

    if (!fromSnapshot) {
      throw new Error('Can not find the current version of the doc.');
    }

    const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin);
    await this.pushDocUpdates(spaceId, docId, [change], editorId);
    // force create a new history record after rollback
    await this.createDocHistory(fromSnapshot, true);
  }

  abstract getSpaceDocTimestamps(
    spaceId: string,
    after?: number
  ): Promise<Record<string, number> | null>;

View on GitHub (pinned to 26c515e050)

Solutions

  1. Before rollback, fetch the available history list for the doc and pass only a timestamp that appears in it (the UI should already restrict to known snapshots).
  2. Increase the history retention window / snapshot cadence if legitimate old versions are being requested and pruned too aggressively.
  3. Verify the spaceId/docId match the doc whose history the user is browsing; a wrong workspace header is a frequent cause.
  4. Convert this to a typed NotFound-style error (e.g. extend a RollbackTargetNotFound) and map to 404 at the resolver so the client can render a clean 'version no longer available' message.

Example fix

// before
const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);
if (!toSnapshot) {
  throw new Error('Can not find the version to rollback to.');
}

// after
const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);
if (!toSnapshot) {
  throw new NotFoundException(
    `No history snapshot for doc ${docId} at ${new Date(timestamp).toISOString()}`
  );
}
Defensive patterns

Strategy: validation

Validate before calling

async function getValidRollbackTarget(storage, ws, doc, ts) {
  const history = await storage.getDocHistoryList(ws, doc); // list available snapshots
  const match = history.find(h => h.timestamp === ts);
  if (!match) {
    throw new UserError(`No snapshot at ${ts}; pick from ${history.map(h => h.timestamp).join(',')}`);
  }
  return match;
}

Type guard

function isRollbackTargetMissing(e: unknown): boolean {
  return e instanceof Error && e.message === 'Can not find the version to rollback to.';
}

Try / catch

try {
  await doc.rollbackDoc(ws, doc, ts);
} catch (e) {
  if (isRollbackTargetMissing(e)) {
    return res.status(404).send('That version is no longer available');
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling rollback with a timestamp older than the oldest retained history record, a timestamp in the future, a timestamp for a doc whose history was pruned/garbage-collected, or a docId/spaceId mismatch where history simply doesn't exist for that pair.

Common situations: History retention policy trimmed old snapshots below the requested timestamp. Clock skew between client (which picked a timestamp from a UI timeline) and server. Doc was imported/migrated and has no history lineage. A user picked a history entry from a different doc.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/d106c5d7d03afbdf. Report an issue: GitHub.