{"record":{"id":"d106c5d7d03afbdf","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":"Error","httpStatus":null,"severity":"error","filePath":"packages/backend/server/src/core/doc/storage/doc.ts","lineNumber":248,"sourceCode":"  abstract pushDocUpdates(\n    spaceId: string,\n    docId: string,\n    updates: Uint8Array[],\n    editorId?: string\n  ): Promise<number>;\n\n  abstract deleteDoc(spaceId: string, docId: string): Promise<void>;\n  abstract deleteSpace(spaceId: string): Promise<void>;\n  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 Error('Can not find the version to rollback to.');\n    }\n\n    const fromSnapshot = await this.getDocSnapshot(spaceId, docId);\n\n    if (!fromSnapshot) {\n      throw new Error('Can not find the current version of the doc.');\n    }\n\n    const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin);\n    await this.pushDocUpdates(spaceId, docId, [change], editorId);\n    // force create a new history record after rollback\n    await this.createDocHistory(fromSnapshot, true);\n  }\n\n  abstract getSpaceDocTimestamps(\n    spaceId: string,\n    after?: number\n  ): Promise<Record<string, number> | null>;","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc/storage/doc.ts#L230-L266","documentation":"Thrown during rollbackDoc when getDocHistory(spaceId, docId, timestamp) returns no snapshot for the requested timestamp. The rollback algorithm needs a 'from' (current) and 'to' (target) snapshot to compute a delta update; without the target it cannot generate the change. This is a plain `new Error` and surfaces as a 500 unless the resolver wraps it.","triggerScenarios":"Calling rollback with a timestamp older than the oldest retained history record, a timestamp in the future, a timestamp for a doc whose history was pruned/garbage-collected, or a docId/spaceId mismatch where history simply doesn't exist for that pair.","commonSituations":"History retention policy trimmed old snapshots below the requested timestamp. Clock skew between client (which picked a timestamp from a UI timeline) and server. Doc was imported/migrated and has no history lineage. A user picked a history entry from a different doc.","solutions":["Before rollback, fetch the available history list for the doc and pass only a timestamp that appears in it (the UI should already restrict to known snapshots).","Increase the history retention window / snapshot cadence if legitimate old versions are being requested and pruned too aggressively.","Verify the spaceId/docId match the doc whose history the user is browsing; a wrong workspace header is a frequent cause.","Convert this to a typed NotFound-style error (e.g. extend a RollbackTargetNotFound) and map to 404 at the resolver so the client can render a clean 'version no longer available' message."],"exampleFix":"// before\nconst toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);\nif (!toSnapshot) {\n  throw new Error('Can not find the version to rollback to.');\n}\n\n// after\nconst toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);\nif (!toSnapshot) {\n  throw new NotFoundException(\n    `No history snapshot for doc ${docId} at ${new Date(timestamp).toISOString()}`\n  );\n}","handlingStrategy":"validation","validationCode":"async function getValidRollbackTarget(storage, ws, doc, ts) {\n  const history = await storage.getDocHistoryList(ws, doc); // list available snapshots\n  const match = history.find(h => h.timestamp === ts);\n  if (!match) {\n    throw new UserError(`No snapshot at ${ts}; pick from ${history.map(h => h.timestamp).join(',')}`);\n  }\n  return match;\n}","typeGuard":"function isRollbackTargetMissing(e: unknown): boolean {\n  return e instanceof Error && e.message === 'Can not find the version to rollback to.';\n}","tryCatchPattern":"try {\n  await doc.rollbackDoc(ws, doc, ts);\n} catch (e) {\n  if (isRollbackTargetMissing(e)) {\n    return res.status(404).send('That version is no longer available');\n  }\n  throw e;\n}","preventionTips":["Only offer timestamps returned by the history list endpoint.","Tune history retention to match user expectations of how far back they can roll.","Pass server-side UTC timestamps, not client-local times."],"tags":["rollback","history","doc","validation","not-found"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}