toeverything/AFFiNE · error · NotFoundException

Workspace ${workspaceId} not found or has no root document

Error message

Workspace ${workspaceId} not found or has no root document

What it means

NotFoundException thrown by createDoc when the workspace's root document (docId === workspaceId) has no bin. AFFiNE stores workspace metadata — including the pages index — inside the root doc, so a missing/empty root doc means the workspace either doesn't exist or wasn't initialized. This is correctly typed as NotFoundException (likely a 404 at the resolver).

Source

Thrown at packages/backend/server/src/core/doc/writer.ts:64

   * Creates a new document from markdown content.
   *
   * @param workspaceId - The workspace ID
   * @param title - The document title
   * @param markdown - The markdown content (body only)
   * @param editorId - Optional editor ID for tracking
   * @returns The created document ID
   */
  async createDoc(
    workspaceId: string,
    title: string,
    markdown: string,
    editorId?: string
  ): Promise<CreateDocResult> {
    // Fetch workspace root doc first - reject if not found
    // The root doc (docId = workspaceId) contains meta.pages array
    const rootDoc = await this.storage.getDoc(workspaceId, workspaceId);
    if (!rootDoc?.bin) {
      throw new NotFoundException(
        `Workspace ${workspaceId} not found or has no root document`
      );
    }

    const rootDocBin = Buffer.isBuffer(rootDoc.bin)
      ? rootDoc.bin
      : Buffer.from(
          rootDoc.bin.buffer,
          rootDoc.bin.byteOffset,
          rootDoc.bin.byteLength
        );

    const docId = nanoid();

    this.logger.debug(
      `Creating doc ${docId} in workspace ${workspaceId} from markdown`
    );

View on GitHub (pinned to 26c515e050)

Solutions

  1. Confirm the workspace exists and is initialized before offering the 'new doc' action (call the workspace resolver / check root doc presence).
  2. If the workspace should exist, inspect its root doc row in storage and re-initialize if bin is empty.
  3. On the client, treat 404 from createDoc as 'workspace gone' and refresh workspace state / re-fetch the workspace list.
  4. Add an id-shape guard (workspaceId is a nanoid/cuid of expected length) to reject obvious garbage early.

Example fix

// before
const rootDoc = await this.storage.getDoc(workspaceId, workspaceId);
if (!rootDoc?.bin) {
  throw new NotFoundException(`Workspace ${workspaceId} not found or has no root document`);
}

// after — distinguish 'no workspace' from 'corrupt root' for cleaner ops
const rootDoc = await this.storage.getDoc(workspaceId, workspaceId);
if (!rootDoc) {
  throw new NotFoundException(`Workspace ${workspaceId} not found`);
}
if (!rootDoc.bin) {
  this.logger.error(`Workspace ${workspaceId} root doc has empty bin`);
  throw new ConflictException(`Workspace ${workspaceId} root document is corrupt`);
}
Defensive patterns

Strategy: validation

Validate before calling

async function assertWorkspaceReady(storage, ws) {
  const root = await storage.getDoc(ws, ws);
  if (!root?.bin) {
    throw new UserError(`Workspace ${ws} not initialized`);
  }
  return root;
}

Type guard

import { NotFoundException } from '@nestjs/common';
function isWorkspaceMissing(e: unknown): boolean {
  return e instanceof NotFoundException && /Workspace .* not found/.test(e.message);
}

Try / catch

try {
  await writer.createDoc(ws, title, md);
} catch (e) {
  if (isWorkspaceMissing(e)) return res.status(404).send('Workspace not found');
  throw e;
}

Prevention

When it happens

Trigger: Calling createDoc with a workspaceId that was never created, was deleted, or whose root doc failed to initialize. Also when the storage adapter returns the row but with an empty/null bin (corrupted or half-written root).

Common situations: Client sends a stale or fabricated workspaceId. A workspace creation job didn't finish writing the root doc. Storage migration left the root doc bin empty. Wrong workspace scoping (e.g. using a docId as workspaceId).

Related errors


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