toeverything/AFFiNE · error · DocNotFound

doc_not_found

doc_not_found

Error message

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

What it means

In rollbackDoc, after the target history snapshot is found, the current doc snapshot is loaded via getDocSnapshot. If the doc has no current snapshot row, rollback cannot capture a pre-rollback history record, so it throws DocNotFound (resource_not_found / doc_not_found) with spaceId and docId.

Solutions

  1. Confirm the doc exists and is readable via the doc read API before offering rollback
  2. Wait for or manually trigger the snapshot merge job so a current snapshot exists
  3. Check the arguments: the first parameter is spaceId (workspace id), the second is docId
  4. If the doc was deleted, remove it from the client's local state instead of rolling back

Example fix

// before
await docAdapter.rollbackDoc(spaceId, docId, ts);

// after
const snap = await docAdapter.getDocSnapshot(spaceId, docId);
if (!snap) {
  throw new Error(`doc ${docId} missing snapshot; wait for merge or recreate doc`);
}
await docAdapter.rollbackDoc(spaceId, docId, ts);
Defensive patterns

Strategy: validation

Validate before calling

const snapshot = await docAdapter.getDocSnapshot(spaceId, docId);
if (!snapshot?.bin) {
  throw new Error(`doc ${docId} has no snapshot; cannot roll back`);
}
await rollbackDoc(spaceId, docId, ts);

Type guard

function isDocNotFound(e: unknown): boolean {
  return (e as { extensions?: { code?: string } }).extensions?.code === 'doc_not_found';
}

Try / catch

try {
  await rollbackDoc(spaceId, docId, ts);
} catch (e) {
  if (isDocNotFound(e)) {
    removeDocFromLocalState(docId); // doc deleted; drop stale reference
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Rolling back a doc that was deleted (snapshot rows removed with the workspace/doc); a doc that only has pending updates but whose snapshot merge job (doc.mergePendingDocUpdates) has never produced a snapshot; passing a docId from a different workspace or swapping spaceId/docId arguments.

Common situations: Stale doc references in a client after another user deleted the doc; rolling back immediately after doc creation on a heavily delayed job queue; scripts built against older API signatures where spaceId and docId positions were swapped.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

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

    };
  }

  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 2af30773ae)