toeverything/AFFiNE · warning · InvalidHistoryTimestamp

invalid_history_timestamp

invalid_history_timestamp

Error message

Invalid doc history timestamp provided.

What it means

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.

Source

Thrown at packages/backend/server/src/core/workspaces/controller.ts:350

    res.setHeader('content-type', 'application/octet-stream');
    res.send(publicRootDoc);
  }

  @Get('/:id/docs/:guid/histories/:timestamp')
  @CallMetric('controllers', 'workspace_get_history')
  async history(
    @CurrentUser() user: CurrentUser,
    @Param('id') ws: string,
    @Param('guid') guid: string,
    @Param('timestamp') timestamp: string,
    @Res() res: Response
  ) {
    const docId = new DocID(guid, ws);
    let ts;
    try {
      ts = new Date(timestamp);
    } catch {
      throw new InvalidHistoryTimestamp({ timestamp });
    }

    await this.ac.user(user.id).doc(ws, guid).assert('Doc.Read');

    const history = await this.workspace.getDocHistory(
      docId.workspace,
      docId.guid,
      ts.getTime()
    );

    if (history) {
      res.setHeader('content-type', 'application/octet-stream');
      res.setHeader('cache-control', 'private, max-age=2592000, immutable');
      res.send(history.bin);
    } else {
      throw new DocHistoryNotFound({
        spaceId: docId.workspace,
        docId: guid,

View on GitHub (pinned to 26c515e050)

Solutions

  1. Validate the timestamp client-side before calling (Date.parse + Number.isFinite).
  2. Fix the server guard to check Number.isNaN(ts.getTime()) instead of relying on a throw.
  3. Send timestamps as UTC ISO 8601 or integer epoch milliseconds.
  4. URL-encode the timestamp segment.

Example fix

// before (server)
try { ts = new Date(timestamp) } catch { throw new InvalidHistoryTimestamp({ timestamp }) }
// after (server)
ts = new Date(timestamp)
if (Number.isNaN(ts.getTime())) throw new InvalidHistoryTimestamp({ timestamp })
Defensive patterns

Strategy: validation

Validate before calling

function validHistoryTimestamp(ts: string): boolean {
  const t = Date.parse(ts)
  return Number.isFinite(t)
}

Type guard

function isValidTimestamp(v: string): v is string {
  return Number.isFinite(Date.parse(v))
}

Try / catch

try { await fetchHistory(guid, ts) } catch (e) {
  if (e.code === 'invalid_history_timestamp') promptValidDate()
  else throw e
}

Prevention

When it happens

Trigger: 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.

Common situations: Client sends a non-ISO string, a malformed epoch, or an empty timestamp segment due to a URL construction bug.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/b251d365413f729c. Report an issue: GitHub.