{"record":{"id":"a66809db0af01b8c","repo":"toeverything/AFFiNE","slug":"can-not-find-the-version-to-rollback-to","errorCode":null,"errorMessage":"Can not find the version to rollback to.","messagePattern":"Can not find the version to rollback to\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/common/nbstore/src/storage/history.ts","lineNumber":79,"sourceCode":"  abstract upsertDocSnapshot(\n    snapshot: DocRecord,\n    prevSnapshot: DocRecord | null\n  ): Promise<boolean>;\n\n  abstract listHistories(\n    docId: string,\n    filter?: HistoryFilter\n  ): Promise<ListedHistory[]>;\n  abstract getHistory(\n    docId: string,\n    timestamp: Date\n  ): Promise<DocRecord | null>;\n  abstract deleteHistory(docId: string, timestamp: Date): Promise<void>;\n\n  async rollbackDoc(docId: string, timestamp: Date, editor?: string) {\n    const toSnapshot = await this.getHistory(docId, timestamp);\n    if (!toSnapshot) {\n      throw new Error('Can not find the version to rollback to.');\n    }\n\n    const fromSnapshot = await this.getDoc(docId);\n\n    if (!fromSnapshot) {\n      throw new Error('Can not find the current version of the doc.');\n    }\n\n    const change = this.generateRevertUpdate(fromSnapshot.bin, toSnapshot.bin);\n    await this.pushDocUpdate({ docId, bin: change, editor }, 'rollback');\n    // force create a new history record after rollback\n    await this.createHistory(docId, fromSnapshot);\n  }\n\n  // history can only be created upon update pushing.\n  protected abstract createHistory(\n    docId: string,\n    snapshot: DocRecord","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/common/nbstore/src/storage/history.ts#L61-L97","documentation":"DocHistoryManager.rollbackDoc(docId, timestamp) first loads the historical snapshot via getHistory(docId, timestamp); when no history record exists for that doc at/before that timestamp, it throws 'Can not find the version to rollback to.' A rollback needs an actual stored snapshot to revert to — it cannot reconstruct one.","triggerScenarios":"Calling rollbackDoc with a timestamp for which no history was ever created (histories are only created on update pushes — see the createHistory contract comment); passing a timestamp older than the earliest snapshot; history retention/expiration removed the record; timestamp precision mismatch (ms vs s) so no record matches.","commonSituations":"UI history panel showing a stale list after history records expired or were deleted (deleteHistory); rolling back a doc that was created but never updated (no snapshots); clock/timezone handling producing wrong timestamps; self-hosted server with history cleanup jobs pruning old snapshots.","solutions":["Verify the timestamp exists in history first: call listHistory(docId) and match against the entries the user actually sees.","Use the exact timestamp from a ListedHistory entry rather than deriving/computing one.","If the record was legitimately deleted/expired, surface 'version no longer available' — there is nothing to roll back to.","Ensure the doc has been updated at least once after the target point, since history records are only created on update pushes."],"exampleFix":"// before\nawait history.rollbackDoc(docId, pickedTimestamp);\n\n// after\nconst list = await history.listHistory(docId);\nconst entry = list.find(h => h.timestamp.getTime() === pickedTimestamp.getTime());\nif (!entry) {\n  toast('This version is no longer available.');\n} else {\n  await history.rollbackDoc(docId, entry.timestamp);\n}","handlingStrategy":"validation","validationCode":"const versions = await history.listHistory(docId);\nconst target = versions.find(v => v.timestamp.getTime() === chosen.getTime());\nif (!target) throw new Error('version not available — refresh the history list');\nawait history.rollbackDoc(docId, target.timestamp);","typeGuard":"const hasVersionAt = (list: ListedHistory[], ts: Date): boolean =>\n  list.some(v => v.timestamp.getTime() === ts.getTime());","tryCatchPattern":"try { await history.rollbackDoc(docId, ts); } catch (e) { if (e instanceof Error && e.message === 'Can not find the version to rollback to.') { /* refresh listHistory, show 'version unavailable' */ } throw e; }","preventionTips":["Drive rollback from timestamps returned by listHistory, never computed ones","Refresh the history list right before rollback in long-open UIs","Remember: history records only exist for updates — a never-updated doc has none"],"tags":["history","snapshot","rollback","affine"],"backgroundTag":"snapshot-version-not-found","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}