toeverything/AFFiNE · error · DocHistoryNotFound

doc_history_not_found

doc_history_not_found

Error message

History of ${docId} at ${timestamp} under Space ${spaceId}.

What it means

rollbackDoc acquires the per-doc update mutex, then calls getDocHistory(spaceId, docId, timestamp) to find a history snapshot at or before the requested timestamp. If no history record exists it throws DocHistoryNotFound (resource_not_found / doc_history_not_found) carrying spaceId, docId, and the requested timestamp.

Solutions

  1. Fetch the doc's available history timestamps first (history list endpoint) and pick one that actually exists
  2. Use server-issued timestamps exactly as returned by the history API — do not round, clamp, or generate them client-side
  3. Verify the doc actually has history records before showing a rollback action
  4. If early history was pruned, roll back to the oldest remaining snapshot instead

Example fix

// before
await workspaceApi.rollbackDoc(spaceId, docId, pickedTimestamp);

// after
const history = await workspaceApi.getDocHistory(spaceId, docId);
const target = history.atimestamps?.find(t => t === pickedTimestamp);
if (!target) throw new UserError('pick a listed history point');
await workspaceApi.rollbackDoc(spaceId, docId, target);
Defensive patterns

Strategy: validation

Validate before calling

// only offer rollback points that actually exist
const history = await listDocHistory(spaceId, docId);
const validTs = (history.timestamps ?? []).filter(t => t <= Date.now());
if (!validTs.includes(requestedTs)) throw new Error('pick a listed history timestamp');

Type guard

function isDocHistoryNotFound(e: unknown): boolean {
  const err = e as { extensions?: { code?: string } };
  return err.extensions?.code === 'doc_history_not_found';
}

Try / catch

try {
  await rollbackDoc(spaceId, docId, ts);
} catch (e) {
  if (isDocHistoryNotFound(e)) {
    refreshHistoryPanel(docId); // oldest point no longer exists; reload list
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling doc rollback with a timestamp earlier than the oldest stored history record; a timestamp that came from a different doc or was computed client-side from the wrong clock; history records removed by retention/GC; rolling back a doc that has never been snapshotted because no edits happened since creation.

Common situations: UI history panels passing a bucketed/rounded timestamp that predates the first snapshot; users scrolling to the very beginning of a long-lived doc whose early history was pruned; replaying an old rollback request after history cleanup.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/d0ac5fc86b4556e7. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/doc/adapters/workspace.ts:261

    return {
      spaceId: workspaceId,
      docId,
      bin: history.blob,
      timestamp: history.timestamp,
      editor: history.editor?.id,
    };
  }

  override 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 DocHistoryNotFound({ spaceId, docId, timestamp });
    }

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

    if (!fromSnapshot) {
      throw new DocNotFound({ spaceId, docId });
    }

    // force create a new history record after rollback
    await this.createDocHistory(
      {
        ...fromSnapshot,
        // override the editor to the one who requested the rollback
        editor: editorId,
      },
      true
    );
    // WARN:

View on GitHub (pinned to 2af30773ae)