{"record":{"id":"b69a5fa96cafa933","repo":"toeverything/AFFiNE","slug":"doc-update-blocked","errorCode":"doc_update_blocked","errorMessage":"Doc ${docId} under Space ${spaceId} is blocked from updating.","messagePattern":"Doc (.+?) under Space (.+?) is blocked from updating\\.","errorType":"exception","errorClass":"DocUpdateBlocked","httpStatus":403,"severity":"warning","filePath":"packages/backend/server/src/core/sync/gateway.ts","lineNumber":966,"sourceCode":"    private readonly docReader: DocReader,\n    private readonly models: Models\n  ) {\n    super(SpaceType.Workspace, client, storage);\n  }\n\n  override async push(\n    spaceId: string,\n    docId: string,\n    updates: Buffer[],\n    editorId: string\n  ) {\n    const docMeta = await this.models.doc.getMeta(spaceId, docId, {\n      select: {\n        blocked: true,\n      },\n    });\n    if (docMeta?.blocked) {\n      throw new DocUpdateBlocked({ spaceId, docId });\n    }\n    return await super.push(spaceId, docId, updates, editorId);\n  }\n\n  override async diff(\n    spaceId: string,\n    docId: string,\n    stateVector?: Uint8Array\n  ) {\n    return await this.docReader.getDocDiff(spaceId, docId, stateVector);\n  }\n\n  async assertAccessible(\n    spaceId: string,\n    userId: string,\n    action: WorkspaceAction\n  ) {\n    await this.ac.user(userId).workspace(spaceId).assert(action);","sourceCodeStart":948,"sourceCodeEnd":984,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/sync/gateway.ts#L948-L984","documentation":"Thrown by WorkspaceSyncAdapter.push() when the doc's metadata row has blocked === true. The block flag is read from the doc meta model before any update is written, so a blocked doc rejects all incoming Yjs updates for that (spaceId, docId). The check is specific to the Workspace space type; Userspace pushes bypass it.","triggerScenarios":"Client emits 'space:push-doc-update' against a workspace doc whose doc_meta.blocked column is true, while still being joined and permissioned. Triggered when an admin/migration has flagged the doc read-only.","commonSituations":"Doc was blocked during a migration, a quota enforcement action, a legal/admin hold, or because it was merged into another doc; client still has the doc open and keeps sending edits.","solutions":["Check whether the doc is intentionally blocked via admin tooling before escalating.","On the client, surface a read-only / locked indicator and stop pushing updates for that docId when this error is received.","If the block is stale, clear doc_meta.blocked for the (spaceId, docId) in the database.","Have the client re-fetch doc metadata on reconnect to learn the blocked state proactively."],"exampleFix":"// before — keep retrying pushes on failure\nsocket.emit('space:push-doc-update', payload);\n\n// after — honor the blocked state\ntry {\n  await socket.emitWithAck('space:push-doc-update', payload);\n} catch (e) {\n  if (e.code === 'doc_update_blocked') {\n    setDocReadOnly(spaceId, docId, true);\n    return;\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Optionally pre-fetch doc meta to learn blocked state\nconst meta = await fetchDocMeta(spaceId, docId);\nif (meta?.blocked) { setReadOnly(docId); }","typeGuard":"function isDocBlocked(meta?: { blocked?: boolean }): boolean {\n  return Boolean(meta?.blocked);\n}","tryCatchPattern":"try {\n  await socket.emitWithAck('space:push-doc-update', payload);\n} catch (e) {\n  if (e?.code === 'doc_update_blocked') { setDocReadOnly(spaceId, docId, true); return; }\n  throw e;\n}","preventionTips":["Surface a read-only UI state when the doc is blocked instead of retrying pushes.","Re-fetch doc meta on reconnect to detect newly-blocked docs.","Stop queuing updates for blocked docs to avoid backpressure."],"tags":["sync","doc-storage","workspace","permissions"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}