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
Thrown by WorkspacesController.history when workspace.getDocHistory(workspaceId, guid, ts.getTime()) returns nothing — permission passed but no history snapshot exists at that timestamp. Served with private immutable caching when found; this error is the empty-case 404.
Solutions
- List available history entries first and use an exact timestamp from that list
- Verify the snapshot/history job actually runs for the workspace
- Pick a timestamp after the doc's creation time
- In clients, catch this and fall back to the current doc binary
Defensive patterns
Strategy: validation
Validate before calling
// choose from timestamps the server actually has, instead of guessing
const histories = await listDocHistories(wsId, guid); // available snapshots
if (histories.length === 0) {
showNoHistoryAvailable(guid);
return;
}
const target = histories.find(h => Math.abs(h.timestamp - wanted) < 60_000) ?? histories[0];
return fetchHistory(wsId, guid, target.timestamp); Type guard
function isDocHistoryNotFound(e: unknown): e is { code: 'doc_history_not_found'; docId: string; spaceId: string; timestamp: number } {
return typeof e === 'object' && e !== null && (e as any).code === 'doc_history_not_found';
} Try / catch
try {
return await getDocHistory(wsId, guid, ts);
} catch (e) {
if (isDocHistoryNotFound(e)) {
return getCurrentDocBinary(wsId, guid); // fall back to latest state
}
throw e;
} Prevention
- Drive the history UI from the history list endpoint; never synthesize timestamps
- Confirm the snapshot job is enabled and running before advertising history features
- Use timestamps at/after the doc's creation time
- Validate the timestamp param (NaN ms silently lands here too)
When it happens
Trigger: GET /api/workspaces/:id/docs/:guid/histories/:timestamp for a timestamp where no snapshot was captured: before the doc's first snapshot, after retention cleaned histories, snapshotting disabled for the workspace, or NaN ms from an unparseable timestamp (see the dead guard on the previous line).
Common situations: Using an arbitrary or rounded timestamp instead of one from the history list; clock-skewed client timestamps; snapshot cron never ran on self-host; timestamp predates doc creation.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- doc_not_found
- Can not find the current version of the doc.
- Can not find the version to rollback to.
- doc_not_found
- doc_not_found
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/165e3f218bd15235.
Report an issue: GitHub.
Appendix: source
Thrown at packages/backend/server/src/core/workspaces/controller.ts:366
ts = new Date(timestamp);
} catch {
throw new InvalidHistoryTimestamp({ timestamp });
}
await this.ac.user(user.id).doc(ws, guid).assert('Doc.Read');
const history = await this.workspace.getDocHistory(
docId.workspace,
docId.guid,
ts.getTime()
);
if (history) {
res.setHeader('content-type', 'application/octet-stream');
res.setHeader('cache-control', 'private, max-age=2592000, immutable');
res.send(history.bin);
} else {
throw new DocHistoryNotFound({
spaceId: docId.workspace,
docId: guid,
timestamp: ts.getTime(),
});
}
}
@Get('/:id/docs/:docId/comment-attachments/:key')
@CallMetric('controllers', 'workspace_get_comment_attachment')
async commentAttachment(
@CurrentUser() user: CurrentUser,
@Param('id') workspaceId: string,
@Param('docId') docId: string,
@Param('key') key: string,
@Res() res: Response
) {
await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Read');
View on GitHub (pinned to b4c8548c09)