{"record":{"id":"d0ac5fc86b4556e7","repo":"toeverything/AFFiNE","slug":"doc-history-not-found","errorCode":"doc_history_not_found","errorMessage":"History of ${docId} at ${timestamp} under Space ${spaceId}.","messagePattern":"History of (.+?) at (.+?) under Space (.+?)\\.","errorType":"exception","errorClass":"DocHistoryNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":261,"sourceCode":"    return {\n      spaceId: workspaceId,\n      docId,\n      bin: history.blob,\n      timestamp: history.timestamp,\n      editor: history.editor?.id,\n    };\n  }\n\n  override async rollbackDoc(\n    spaceId: string,\n    docId: string,\n    timestamp: number,\n    editorId?: string\n  ): Promise<void> {\n    await using _lock = await this.lockDocForUpdate(spaceId, docId);\n    const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);\n    if (!toSnapshot) {\n      throw new DocHistoryNotFound({ spaceId, docId, timestamp });\n    }\n\n    const fromSnapshot = await this.getDocSnapshot(spaceId, docId);\n\n    if (!fromSnapshot) {\n      throw new DocNotFound({ spaceId, docId });\n    }\n\n    // force create a new history record after rollback\n    await this.createDocHistory(\n      {\n        ...fromSnapshot,\n        // override the editor to the one who requested the rollback\n        editor: editorId,\n      },\n      true\n    );\n    // WARN:","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/doc/adapters/workspace.ts#L243-L279","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nawait workspaceApi.rollbackDoc(spaceId, docId, pickedTimestamp);\n\n// after\nconst history = await workspaceApi.getDocHistory(spaceId, docId);\nconst target = history.atimestamps?.find(t => t === pickedTimestamp);\nif (!target) throw new UserError('pick a listed history point');\nawait workspaceApi.rollbackDoc(spaceId, docId, target);","handlingStrategy":"validation","validationCode":"// only offer rollback points that actually exist\nconst history = await listDocHistory(spaceId, docId);\nconst validTs = (history.timestamps ?? []).filter(t => t <= Date.now());\nif (!validTs.includes(requestedTs)) throw new Error('pick a listed history timestamp');","typeGuard":"function isDocHistoryNotFound(e: unknown): boolean {\n  const err = e as { extensions?: { code?: string } };\n  return err.extensions?.code === 'doc_history_not_found';\n}","tryCatchPattern":"try {\n  await rollbackDoc(spaceId, docId, ts);\n} catch (e) {\n  if (isDocHistoryNotFound(e)) {\n    refreshHistoryPanel(docId); // oldest point no longer exists; reload list\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["doc-history","rollback","not-found","yjs"],"backgroundTag":"entity-not-found","analyzedSha":"2af30773aecd567f09b346e7b72fc69143144057","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}