{"record":{"id":"e863e5f3bfbf1ccb","repo":"toeverything/AFFiNE","slug":"doc-not-found","errorCode":"doc_not_found","errorMessage":"Doc ${docId} under Space ${spaceId} not found.","messagePattern":"Doc (.+?) under Space (.+?) not found\\.","errorType":"exception","errorClass":"DocNotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/doc/adapters/workspace.ts","lineNumber":267,"sourceCode":"    };\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:\n    //  we should never do the snapshot updating in recovering,\n    //  which is not the solution in CRDT.\n    //  let user revert in client and update the data in sync system\n    //    const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin);\n    //    await this.pushDocUpdates(spaceId, docId, [change]);\n","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/2af30773aecd567f09b346e7b72fc69143144057/packages/backend/server/src/core/doc/adapters/workspace.ts#L249-L285","documentation":"In rollbackDoc, after the target history snapshot is found, the current doc snapshot is loaded via getDocSnapshot. If the doc has no current snapshot row, rollback cannot capture a pre-rollback history record, so it throws DocNotFound (resource_not_found / doc_not_found) with spaceId and docId.","triggerScenarios":"Rolling back a doc that was deleted (snapshot rows removed with the workspace/doc); a doc that only has pending updates but whose snapshot merge job (doc.mergePendingDocUpdates) has never produced a snapshot; passing a docId from a different workspace or swapping spaceId/docId arguments.","commonSituations":"Stale doc references in a client after another user deleted the doc; rolling back immediately after doc creation on a heavily delayed job queue; scripts built against older API signatures where spaceId and docId positions were swapped.","solutions":["Confirm the doc exists and is readable via the doc read API before offering rollback","Wait for or manually trigger the snapshot merge job so a current snapshot exists","Check the arguments: the first parameter is spaceId (workspace id), the second is docId","If the doc was deleted, remove it from the client's local state instead of rolling back"],"exampleFix":"// before\nawait docAdapter.rollbackDoc(spaceId, docId, ts);\n\n// after\nconst snap = await docAdapter.getDocSnapshot(spaceId, docId);\nif (!snap) {\n  throw new Error(`doc ${docId} missing snapshot; wait for merge or recreate doc`);\n}\nawait docAdapter.rollbackDoc(spaceId, docId, ts);","handlingStrategy":"validation","validationCode":"const snapshot = await docAdapter.getDocSnapshot(spaceId, docId);\nif (!snapshot?.bin) {\n  throw new Error(`doc ${docId} has no snapshot; cannot roll back`);\n}\nawait rollbackDoc(spaceId, docId, ts);","typeGuard":"function isDocNotFound(e: unknown): boolean {\n  return (e as { extensions?: { code?: string } }).extensions?.code === 'doc_not_found';\n}","tryCatchPattern":"try {\n  await rollbackDoc(spaceId, docId, ts);\n} catch (e) {\n  if (isDocNotFound(e)) {\n    removeDocFromLocalState(docId); // doc deleted; drop stale reference\n    return;\n  }\n  throw e;\n}","preventionTips":["Check the doc still exists before exposing history actions","Keep argument order straight: (spaceId, docId, timestamp, editorId)","Wait for the snapshot merge job on newly created docs before offering rollback"],"tags":["doc","rollback","not-found","snapshot"],"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"}