toeverything/AFFiNE · error

INVALID_DELEGATED_EDITOR_SESSION

Error message

INVALID_DELEGATED_EDITOR_SESSION

What it means

Generic Error('INVALID_DELEGATED_EDITOR_SESSION') thrown by the copilot.delegated.editor.upsert realtime request handler when any of the session invariants fail: no authenticated user, missing connectionId in context, session lookup fails, or the session's userId/workspaceId/docId do not exactly match the input. It protects delegated-editor leases so a lease can only be created against the caller's own session for the exact doc in the request.

Source

Thrown at packages/backend/server/src/plugins/copilot/delegated/realtime.ts:89

            ])
          )
          .max(4),
      })
      .strict();
    this.registry.registerRequest({
      name: 'copilot.delegated.editor.upsert',
      input: leaseInput,
      handle: async (user, input, context) => {
        const session = await this.sessions.get(input.sessionId);
        if (
          !user ||
          !context?.connectionId ||
          !session ||
          session.config.userId !== user.id ||
          session.config.workspaceId !== input.workspaceId ||
          session.config.docId !== input.docId
        ) {
          throw new Error('INVALID_DELEGATED_EDITOR_SESSION');
        }
        const lease = this.delegated.upsert(
          user.id,
          context.connectionId,
          input
        );
        this.event.broadcast('copilot.delegated.editor.upserted', lease);
        return { ok: true, expiresAt: lease.expiresAt };
      },
    });
    this.registry.registerRequest({
      name: 'copilot.delegated.editor.release',
      input: z
        .object({
          clientId: z.string().min(1).max(128),
          editorStateId: z.string().min(1).max(128),
        })
        .strict(),

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Ensure input.sessionId/workspaceId/docId are read from the same live session object the server returned
  2. Re-establish the realtime connection (new connectionId) after reconnect before sending lease upserts
  3. Re-fetch the copilot session and retry the upsert once with fresh values
  4. Handle this error by tearing down the local delegated-editor lease and re-negotiating the session

Example fix

// before
socket.request('copilot.delegated.editor.upsert', {
  sessionId: cachedSessionId, workspaceId: cachedWsId, docId: cachedDocId, // possibly stale
});

// after
const session = await refetchCurrentSession();
if (!session) throw new Error('session gone');
await socket.request('copilot.delegated.editor.upsert', {
  sessionId: session.id,
  workspaceId: session.config.workspaceId,
  docId: session.config.docId,
});
Defensive patterns

Strategy: validation

Validate before calling

const session = await chatSession.get(sessionId);
if (
  !session ||
  session.config.userId !== user.id ||
  session.config.workspaceId !== input.workspaceId ||
  session.config.docId !== input.docId
) {
  throw new Error('Session/context mismatch — refetch before leasing');
}
if (!socket.isConnected || !socket.connectionId) await socket.reconnect();

Try / catch

try {
  await socket.request('copilot.delegated.editor.upsert', input);
} catch (e) {
  if (e.message === 'INVALID_DELEGATED_EDITOR_SESSION') {
    await refetchSessionAndReconnect();
    await socket.request('copilot.delegated.editor.upsert', refreshedInput); // one retry with fresh state
  } else throw e;
}

Prevention

When it happens

Trigger: Sending copilot.delegated.editor.upsert with a sessionId belonging to another user; passing workspaceId or docId that differs from session.config; a dropped/reconnected websocket so context.connectionId is missing; session expired and evicted between listing and upsert.

Common situations: Client state desyncs after doc switch (docId in input is stale); reconnect logic resends the upsert with an old session; concurrent tabs each hold different session ids.

Related errors


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