toeverything/AFFiNE · error · DocNotFound

doc_not_found

doc_not_found

Error message

Doc ${docId} under Space ${spaceId} not found.

What it means

Thrown by `rollbackDoc` after the target history was found, when the *current* snapshot `getDocSnapshot(spaceId, docId)` returns null. Without a 'from' snapshot the rollback cannot seed the post-rollback history record, so it aborts with `DocNotFound` (category `resource_not_found`) interpolating `docId` and `spaceId`.

Source

Thrown at packages/backend/server/src/core/doc/adapters/workspace.ts:213

    };
  }

  override async rollbackDoc(
    spaceId: string,
    docId: string,
    timestamp: number,
    editorId?: string
  ): Promise<void> {
    await using _lock = await this.lockDocForUpdate(spaceId, docId);
    const toSnapshot = await this.getDocHistory(spaceId, docId, timestamp);
    if (!toSnapshot) {
      throw new DocHistoryNotFound({ spaceId, docId, timestamp });
    }

    const fromSnapshot = await this.getDocSnapshot(spaceId, docId);

    if (!fromSnapshot) {
      throw new DocNotFound({ spaceId, docId });
    }

    // force create a new history record after rollback
    await this.createDocHistory(
      {
        ...fromSnapshot,
        // override the editor to the one who requested the rollback
        editor: editorId,
      },
      true
    );
    // WARN:
    //  we should never do the snapshot updating in recovering,
    //  which is not the solution in CRDT.
    //  let user revert in client and update the data in sync system
    //    const change = this.generateChangeUpdate(fromSnapshot.bin, toSnapshot.bin);
    //    await this.pushDocUpdates(spaceId, docId, [change]);

View on GitHub (pinned to 26c515e050)

Solutions

  1. Verify the current doc snapshot row exists in the workspace's `doc` table before offering rollback.
  2. If the snapshot is genuinely gone but history exists, restore a snapshot from history into the `doc` table first (data repair), then retry rollback.
  3. Treat `doc_not_found` from rollback as a data-integrity incident — inspect both tables for the `spaceId`/`docId`.
  4. Prevent snapshot deletion from outpacing history cleanup in retention jobs.

Example fix

// before
await adapter.rollbackDoc(spaceId, docId, timestamp, editorId);

// after
const current = await adapter.getDocSnapshot(spaceId, docId);
if (!current) {
  throw new Error(
    `Cannot roll back ${docId}: live snapshot missing (history exists). Data repair required.`
  );
}
await adapter.rollbackDoc(spaceId, docId, timestamp, editorId);
Defensive patterns

Strategy: validation

Validate before calling

// Verify the current snapshot exists before attempting rollback
const current = await adapter.getDocSnapshot(spaceId, docId);
if (!current) {
  throw new Error(
    `Cannot roll back ${docId}: live snapshot missing. Data repair required.`,
  );
}

await adapter.rollbackDoc(spaceId, docId, timestamp, editorId);

Type guard

function isDocSnapshot(value: unknown): value is { docId: string; bin: Uint8Array; timestamp: number } {
  return typeof value === 'object' && value !== null &&
    typeof (value as { docId?: unknown }).docId === 'string' &&
    (value as { bin?: unknown }).bin instanceof Uint8Array;
}

Try / catch

try {
  await adapter.rollbackDoc(spaceId, docId, timestamp, editorId);
} catch (e) {
  if ((e as { code?: string }).code === 'doc_not_found') {
    // data-integrity incident: snapshot missing while history exists
    alertOps('snapshot_missing_on_rollback', { spaceId, docId });
    throw e;
  }
  throw e;
}

Prevention

When it happens

Trigger: Rolling back a doc whose current snapshot row is missing even though history rows exist — e.g., the live snapshot was deleted/pruned while history was retained, or the doc was deleted but history lingered.

Common situations: Inconsistent state between the `doc` (snapshot) and `history` tables after a partial cleanup/migration; a doc soft-deleted before history expiry; manual DB edits that removed the snapshot.

Related errors


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