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
- Fetch the doc's available history timestamps first (history list endpoint) and pick one that actually exists
- Use server-issued timestamps exactly as returned by the history API — do not round, clamp, or generate them client-side
- Verify the doc actually has history records before showing a rollback action
- 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
- Always use server-issued history timestamps verbatim
- Refresh the history list right before rollback in long-lived UIs
- Hide the rollback button for docs with no history records
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)