{"record":{"id":"2b85331e82701ac1","repo":"overleaf/overleaf","slug":"requested-version-past-the-end-of-the-history","errorCode":null,"errorMessage":"requested version past the end of the history","messagePattern":"requested version past the end of the history","errorType":"exception","errorClass":"BadRequestError","httpStatus":400,"severity":"warning","filePath":"services/project-history/app/js/SnapshotManager.js","lineNumber":238,"sourceCode":"  const changes = chunk.getChanges()\n  snapshot.applyAll(changes)\n  return {\n    snapshot,\n    version: chunk.getEndVersion(),\n  }\n}\n\nasync function getChangesInChunkSince(projectId, historyId, sinceVersion) {\n  const latestChunk = Core.Chunk.fromRaw(\n    (\n      await HistoryStoreManager.promises.getMostRecentChunk(\n        projectId,\n        historyId\n      )\n    ).chunk\n  )\n  if (sinceVersion > latestChunk.getEndVersion()) {\n    throw new Errors.BadRequestError(\n      'requested version past the end of the history'\n    )\n  }\n  const latestStartVersion = latestChunk.getStartVersion()\n  let chunk = latestChunk\n  if (sinceVersion < latestStartVersion) {\n    chunk = Core.Chunk.fromRaw(\n      (\n        await HistoryStoreManager.promises.getChunkAtVersion(\n          projectId,\n          historyId,\n          sinceVersion\n        )\n      ).chunk\n    )\n  }\n  const changes = chunk\n    .getChanges()","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/overleaf/overleaf/blob/28ad3b03b71cb4311decdcb55c36b33ec10d72db/services/project-history/app/js/SnapshotManager.js#L220-L256","documentation":"getChangesInChunkSince throws Errors.BadRequestError when sinceVersion is greater than the latest chunk's end version, meaning the caller asked for changes since a version that is beyond the end of the project's recorded history. It protects against invalid version queries.","triggerScenarios":"Calling getChangesInChunkSince(projectId, historyId, sinceVersion) where sinceVersion > latestChunk.getEndVersion() — e.g. a stale client sends a version from a different/forked history or an out-of-range number.","commonSituations":"Clients caching a version from before a history reset or resync; hard resync rewrote history so old versions no longer exist; off-by-one or wrong project's version sent to another project's history; replaying updates after history truncation.","solutions":["Clamp/validate sinceVersion against the history's end version before calling.","After a hard resync, clients must re-fetch the current version instead of reusing old ones.","Return 400 to the caller and have it reset its local version tracking.","Verify the correct historyId is being used for the project."],"exampleFix":"// before\nconst changes = await SnapshotManager.promises.getChangesInChunkSince(projectId, historyId, sinceVersion)\n// after\nconst latestChunk = (await ChunkManager.promises.getLatestChunk(projectId, historyId)).chunk\nif (sinceVersion > latestChunk.getEndVersion()) {\n  sinceVersion = latestChunk.getEndVersion() // or reset client state\n}\nconst changes = await SnapshotManager.promises.getChangesInChunkSince(projectId, historyId, sinceVersion)","handlingStrategy":"validation","validationCode":"const latestChunk = (await ChunkManager.promises.getLatestChunk(projectId, historyId)).chunk\nif (sinceVersion > latestChunk.getEndVersion()) {\n  sinceVersion = latestChunk.getEndVersion()\n}","typeGuard":null,"tryCatchPattern":"try {\n  await SnapshotManager.promises.getChangesInChunkSince(projectId, historyId, sinceVersion)\n} catch (err) {\n  if (err instanceof Errors.BadRequestError && err.message.includes('past the end')) {\n    // reset client version tracking and re-sync from latest\n  } else {\n    throw err\n  }\n}","preventionTips":["Clamp sinceVersion to the history's end version before querying.","Invalidate cached versions after resyncs/history rewrites.","Always pair version queries with the correct historyId.","Have clients re-fetch latest version on 400 responses."],"tags":["bad-request","version","history","validation"],"backgroundTag":"version-out-of-range","analyzedSha":"28ad3b03b71cb4311decdcb55c36b33ec10d72db","analyzedAt":"2026-09-03T02:10:22.807Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T07:17:11.731Z"}