toeverything/AFFiNE · error · NotInSpace
not_in_space
not_in_space
Error message
You should join in Space ${spaceId} before broadcasting messages. What it means
Thrown by SyncSocketAdapter.assertIn() when the socket client has not joined the room for the target space. assertIn() is invoked by push(), diff(), delete(), and getTimestamps(), so any sync data operation attempted before the client joined the space room will fail. Membership is tracked via this.client.rooms.has(roomId); the room key is `${spaceType}:${spaceId}:${roomType}`.
Source
Thrown at packages/backend/server/src/core/sync/gateway.ts:907
}
await this.assertAccessible(spaceId, userId, 'Workspace.Sync');
return this.client.join(this.room(spaceId, roomType));
}
async leave(spaceId: string, roomType: RoomType = 'sync') {
if (!this.in(spaceId, roomType)) {
return;
}
return this.client.leave(this.room(spaceId, roomType));
}
in(spaceId: string, roomType: RoomType = 'sync') {
return this.client.rooms.has(this.room(spaceId, roomType));
}
assertIn(spaceId: string, roomType: RoomType = 'sync') {
if (!this.client.rooms.has(this.room(spaceId, roomType))) {
throw new NotInSpace({ spaceId });
}
}
abstract assertAccessible(
spaceId: string,
userId: string,
action: WorkspaceAction
): Promise<void>;
async push(
spaceId: string,
docId: string,
updates: Buffer[],
editorId: string
) {
this.assertIn(spaceId);
return await this.storage.pushDocUpdates(spaceId, docId, updates, editorId);
}View on GitHub (pinned to 26c515e050)
Solutions
- Always emit 'space:join' and await its acknowledgment before sending any push/diff/delete/timestamps messages for that space.
- On socket reconnect, re-join all previously joined spaces before resuming sync.
- Gate outbound sync messages behind a per-space 'joined' flag set on join ack and cleared on leave/disconnect.
Example fix
// before
socket.emit('space:push-doc-update', { spaceType, spaceId, docId, update });
// after — await join before pushing
await socket.emitWithAck('space:join', { spaceType, spaceId });
joinedSpaces.add(spaceId);
socket.emit('space:push-doc-update', { spaceType, spaceId, docId, update }); Defensive patterns
Strategy: validation
Validate before calling
// Track joined spaces and assert before any data op
if (!joinedSpaces.has(`${spaceType}:${spaceId}`)) {
await socket.emitWithAck('space:join', { spaceType, spaceId });
joinedSpaces.add(`${spaceType}:${spaceId}`);
} Type guard
function isJoined(joined: Set<string>, spaceType: string, spaceId: string): boolean {
return joined.has(`${spaceType}:${spaceId}`);
} Try / catch
try {
await operation();
} catch (e) {
if (e?.code === 'not_in_space') { await join(spaceType, spaceId); await operation(); return; }
throw e;
} Prevention
- Await the 'space:join' ack before sending any push/diff/delete/timestamps.
- Re-join all spaces on reconnect.
- Maintain a per-socket joined-spaces set cleared on disconnect.
When it happens
Trigger: Client emits 'space:push-doc-update', 'space:diff-doc-update', 'space:delete-doc', or 'space:get-doc-timestamps' before emitting 'space:join' for that spaceId, or after the client was disconnected and reconnected without re-joining.
Common situations: Client boot sequence orders data operations before the join handshake completes; reconnection logic forgets to re-join rooms; client switches spaces and sends a leftover update for the previous space after leaving it.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/ff373fd85b5233e9.
Report an issue: GitHub.