{"record":{"id":"165e3f218bd15235","repo":"toeverything/AFFiNE","slug":"doc-history-not-found-165e3f","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/workspaces/controller.ts","lineNumber":366,"sourceCode":"      ts = new Date(timestamp);\n    } catch {\n      throw new InvalidHistoryTimestamp({ timestamp });\n    }\n\n    await this.ac.user(user.id).doc(ws, guid).assert('Doc.Read');\n\n    const history = await this.workspace.getDocHistory(\n      docId.workspace,\n      docId.guid,\n      ts.getTime()\n    );\n\n    if (history) {\n      res.setHeader('content-type', 'application/octet-stream');\n      res.setHeader('cache-control', 'private, max-age=2592000, immutable');\n      res.send(history.bin);\n    } else {\n      throw new DocHistoryNotFound({\n        spaceId: docId.workspace,\n        docId: guid,\n        timestamp: ts.getTime(),\n      });\n    }\n  }\n\n  @Get('/:id/docs/:docId/comment-attachments/:key')\n  @CallMetric('controllers', 'workspace_get_comment_attachment')\n  async commentAttachment(\n    @CurrentUser() user: CurrentUser,\n    @Param('id') workspaceId: string,\n    @Param('docId') docId: string,\n    @Param('key') key: string,\n    @Res() res: Response\n  ) {\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Read');\n","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/core/workspaces/controller.ts#L348-L384","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// choose from timestamps the server actually has, instead of guessing\nconst histories = await listDocHistories(wsId, guid); // available snapshots\nif (histories.length === 0) {\n  showNoHistoryAvailable(guid);\n  return;\n}\nconst target = histories.find(h => Math.abs(h.timestamp - wanted) < 60_000) ?? histories[0];\nreturn fetchHistory(wsId, guid, target.timestamp);","typeGuard":"function isDocHistoryNotFound(e: unknown): e is { code: 'doc_history_not_found'; docId: string; spaceId: string; timestamp: number } {\n  return typeof e === 'object' && e !== null && (e as any).code === 'doc_history_not_found';\n}","tryCatchPattern":"try {\n  return await getDocHistory(wsId, guid, ts);\n} catch (e) {\n  if (isDocHistoryNotFound(e)) {\n    return getCurrentDocBinary(wsId, guid); // fall back to latest state\n  }\n  throw e;\n}","preventionTips":["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)"],"tags":["not-found","history","snapshot","doc"],"backgroundTag":"resource-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"}