{"record":{"id":"72c8300fd24b2d73","repo":"toeverything/AFFiNE","slug":"not-found","errorCode":"not_found","errorMessage":"Doc not found","messagePattern":"Doc not found","errorType":"exception","errorClass":"NotFound","httpStatus":404,"severity":"error","filePath":"packages/backend/server/src/core/doc-service/controller.ts","lineNumber":33,"sourceCode":"import { DatabaseDocReader } from '../doc';\n\n@Controller('/rpc')\nexport class DocRpcController {\n  private readonly logger = new Logger(DocRpcController.name);\n\n  constructor(private readonly docReader: DatabaseDocReader) {}\n\n  @SkipThrottle()\n  @Internal()\n  @Get('/workspaces/:workspaceId/docs/:docId')\n  async getDoc(\n    @Param('workspaceId') workspaceId: string,\n    @Param('docId') docId: string,\n    @Res() res: Response\n  ) {\n    const doc = await this.docReader.getDoc(workspaceId, docId);\n    if (!doc) {\n      throw new NotFound('Doc not found');\n    }\n    this.logger.debug(\n      `get doc ${docId} from workspace ${workspaceId}, size: ${doc.bin.length}`\n    );\n    res.setHeader('x-doc-timestamp', doc.timestamp.toString());\n    if (doc.editor) {\n      res.setHeader('x-doc-editor-id', doc.editor);\n    }\n    res.send(doc.bin);\n  }\n\n  @SkipThrottle()\n  @Internal()\n  @Get('/workspaces/:workspaceId/docs/:docId/markdown')\n  async getDocMarkdown(\n    @Param('workspaceId') workspaceId: string,\n    @Param('docId') docId: string,\n    @Query('aiEditable') aiEditable?: string","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/doc-service/controller.ts#L15-L51","documentation":"Thrown by the internal RPC handler `GET /rpc/workspaces/:workspaceId/docs/:docId` when `docReader.getDoc(workspaceId, docId)` returns null. The endpoint is `@Internal()` (server-to-server) and maps the missing doc to `NotFound` with code `not_found`, which the global filter turns into HTTP 404.","triggerScenarios":"A peer service (sync/collab) requesting a doc that was never created, was deleted, or whose binary snapshot row is missing in the `doc` table for that workspace.","commonSituations":"Sync requests arriving before the doc snapshot is written; a deleted doc still referenced by cached routing; workspace migration that dropped rows; a malformed `docId`.","solutions":["On the caller side, treat HTTP 404 from this RPC as 'doc absent' and trigger first-create or skip, not as a hard failure.","Verify the doc row exists in the workspace's `doc` table before issuing the RPC for known-existing docs.","Confirm `workspaceId`/`docId` are well-formed and paired (doc belongs to that workspace).","If the doc was just created, retry shortly to allow the snapshot write to land."],"exampleFix":"// before\nconst res = await fetch(`/rpc/workspaces/${wid}/docs/${did}`);\nconst bin = await res.arrayBuffer();\n\n// after\nconst res = await fetch(`/rpc/workspaces/${wid}/docs/${did}`);\nif (res.status === 404) {\n  // doc absent — caller decides: seed it or skip\n  return null;\n}\nif (!res.ok) throw new Error(`doc rpc failed: ${res.status}`);\nconst bin = await res.arrayBuffer();","handlingStrategy":"try-catch","validationCode":"// Internal RPC caller: confirm the snapshot row exists before requesting\nasync function docSnapshotExists(workspaceId: string, docId: string): Promise<boolean> {\n  const row = await models.doc.get(workspaceId, docId);\n  return row != null;\n}\n\nif (!(await docSnapshotExists(workspaceId, docId))) {\n  // doc not seeded yet — handle as 'absent', not an error\n  return null;\n}","typeGuard":"function isDocBytes(value: unknown): value is { bin: Uint8Array; timestamp: number } {\n  return typeof value === 'object' && value !== null &&\n    (value as { bin?: unknown }).bin instanceof Uint8Array &&\n    typeof (value as { timestamp?: unknown }).timestamp === 'number';\n}","tryCatchPattern":"try {\n  const res = await fetch(`/rpc/workspaces/${wid}/docs/${did}`);\n  if (res.status === 404) return null; // doc absent — caller decides\n  if (!res.ok) throw new Error(`doc rpc failed: ${res.status}`);\n  return await res.arrayBuffer();\n} catch (e) {\n  // network-level errors: retry/backoff as appropriate\n  throw e;\n}","preventionTips":["Treat HTTP 404 from `/rpc/.../docs/:docId` as 'doc absent', not a hard failure.","Verify the snapshot row exists for known docs before calling.","Retry shortly after doc creation to let the snapshot write land.","Validate `workspaceId`/`docId` pairing and format."],"tags":["rpc","http","doc-storage","not-found","internal","sync"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}