{"record":{"id":"b251d365413f729c","repo":"toeverything/AFFiNE","slug":"invalid-history-timestamp","errorCode":"invalid_history_timestamp","errorMessage":"Invalid doc history timestamp provided.","messagePattern":"Invalid doc history timestamp provided\\.","errorType":"exception","errorClass":"InvalidHistoryTimestamp","httpStatus":400,"severity":"warning","filePath":"packages/backend/server/src/core/workspaces/controller.ts","lineNumber":350,"sourceCode":"    res.setHeader('content-type', 'application/octet-stream');\n    res.send(publicRootDoc);\n  }\n\n  @Get('/:id/docs/:guid/histories/:timestamp')\n  @CallMetric('controllers', 'workspace_get_history')\n  async history(\n    @CurrentUser() user: CurrentUser,\n    @Param('id') ws: string,\n    @Param('guid') guid: string,\n    @Param('timestamp') timestamp: string,\n    @Res() res: Response\n  ) {\n    const docId = new DocID(guid, ws);\n    let ts;\n    try {\n      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,","sourceCodeStart":332,"sourceCodeEnd":368,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/controller.ts#L332-L368","documentation":"Intended to fire when the history timestamp path param cannot be parsed. NOTE: the current implementation is effectively unreachable because `new Date(timestamp)` never throws; an unparseable string yields an Invalid Date whose getTime() is NaN. So malformed timestamps pass this guard and surface later as an empty history lookup instead of InvalidHistoryTimestamp.","triggerScenarios":"Calling the doc history endpoint with a timestamp path param the author expected to fail Date parsing (e.g. 'abc', '2024-13-99'). In practice the catch does not trigger; the request proceeds with NaN and returns no history.","commonSituations":"Client sends a non-ISO string, a malformed epoch, or an empty timestamp segment due to a URL construction bug.","solutions":["Validate the timestamp client-side before calling (Date.parse + Number.isFinite).","Fix the server guard to check Number.isNaN(ts.getTime()) instead of relying on a throw.","Send timestamps as UTC ISO 8601 or integer epoch milliseconds.","URL-encode the timestamp segment."],"exampleFix":"// before (server)\ntry { ts = new Date(timestamp) } catch { throw new InvalidHistoryTimestamp({ timestamp }) }\n// after (server)\nts = new Date(timestamp)\nif (Number.isNaN(ts.getTime())) throw new InvalidHistoryTimestamp({ timestamp })","handlingStrategy":"validation","validationCode":"function validHistoryTimestamp(ts: string): boolean {\n  const t = Date.parse(ts)\n  return Number.isFinite(t)\n}","typeGuard":"function isValidTimestamp(v: string): v is string {\n  return Number.isFinite(Date.parse(v))\n}","tryCatchPattern":"try { await fetchHistory(guid, ts) } catch (e) {\n  if (e.code === 'invalid_history_timestamp') promptValidDate()\n  else throw e\n}","preventionTips":["Always validate timestamps before sending.","Use ISO 8601 UTC.","URL-encode path segments.","On the server, check Number.isNaN(ts.getTime()) since Date never throws."],"tags":["input-validation","history","date","bug"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}