{"record":{"id":"9f754ec2408cdc51","repo":"toeverything/AFFiNE","slug":"expect-to-publish-doc","errorCode":"expect_to_publish_doc","errorMessage":"Expected to publish a doc, not a Space.","messagePattern":"Expected to publish a doc, not a Space\\.","errorType":"exception","errorClass":"ExpectToPublishDoc","httpStatus":400,"severity":"error","filePath":"packages/backend/server/src/core/workspaces/resolvers/doc.ts","lineNumber":474,"sourceCode":"  @Mutation(() => DocType)\n  async publishDoc(\n    @CurrentUser() user: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('docId') docId: string,\n    @Args({\n      name: 'mode',\n      type: () => PublicDocMode,\n      nullable: true,\n      defaultValue: PublicDocMode.Page,\n    })\n    mode: PublicDocMode\n  ) {\n    if (workspaceId === docId) {\n      this.logger.error('Expect to publish doc, but it is a workspace', {\n        workspaceId,\n        docId,\n      });\n      throw new ExpectToPublishDoc();\n    }\n\n    await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');\n    await this.assertCanShare(user.id, {\n      workspaceId,\n      docId,\n      action: 'publishDoc',\n    });\n\n    const doc = await this.models.doc.publish(workspaceId, docId, mode);\n    this.event.emit('doc.public_state.changed', { workspaceId, docId });\n\n    this.logger.log(\n      `Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`\n    );\n\n    return doc;\n  }","sourceCodeStart":456,"sourceCodeEnd":492,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/workspaces/resolvers/doc.ts#L456-L492","documentation":"Thrown by the publishPage/publishDoc mutation when workspaceId === docId. In AFFiNE the workspaceId is itself the root doc id, so publishing 'the workspace doc' is meaningless - the server rejects it up front and logs at error level. ExpectToPublishDoc is an invalid_input error.","triggerScenarios":"Calling publishDoc with docId equal to workspaceId (e.g. passing the workspace root id where a child page id is expected).","commonSituations":"Frontend bug reusing the workspace id as the page id; user selecting the workspace root in a publish dialog; copy/paste of the workspace id into a doc publish field.","solutions":["Ensure the docId passed to publishDoc is a child page id, never the workspace root id.","Add a client-side guard: disable the publish action when docId === workspaceId.","Fix the upstream selection logic that surfaced the workspace root as a publishable doc."],"exampleFix":"// before\nawait gql.publishDoc({ workspaceId, docId: workspaceId, mode });\n\n// after - guard before calling\nif (docId === workspaceId) {\n  throw new Error('Cannot publish the workspace root doc');\n}\nawait gql.publishDoc({ workspaceId, docId, mode });","handlingStrategy":"validation","validationCode":"// Never publish the workspace root\nif (docId === workspaceId) {\n  throw new Error('Cannot publish the workspace root doc');\n}\nawait gql.publishDoc({ workspaceId, docId, mode });","typeGuard":"function isExpectToPublishDoc(e: unknown): boolean {\n  return (\n    typeof e === 'object' &&\n    e !== null &&\n    (e as any).extensions?.code === 'expect_to_publish_doc'\n  );\n}","tryCatchPattern":"try {\n  await gql.publishDoc({ workspaceId, docId, mode });\n} catch (e) {\n  if (isExpectToPublishDoc(e)) {\n    // bug in selection logic; do not retry, fix the docId source\n    reportBug('publishDoc called with workspaceId as docId');\n    return;\n  }\n  throw e;\n}","preventionTips":["Disable the publish action on the workspace root in the UI.","Keep workspaceId and docId as distinct typed primitives to avoid confusion.","Add an assertion in the client SDK: assert(docId !== workspaceId)."],"tags":["graphql","doc","publishing","validation"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}