OtterMind/Chat2DB · error · BusinessException

ai.chat.history.sessionNotOwned

ai.chat.history.sessionNotOwned

Error message

ai.chat.history.sessionNotOwned

What it means

Thrown by AiChatHistoryServiceImpl.addMessageLocal when ownsSession(userId, sessionId) returns false, i.e. the given sessionId is not present in the current user's sessions file. Arg is {sessionId}. This is an authorization/ownership guard preventing a user from writing messages into another user's chat session. The i18n key is defined but resolves to the raw code if no message is present.

Source

Thrown at chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiChatHistoryServiceImpl.java:121

    private synchronized AiChatSession createSessionLocal(Long userId, String title) {
        AiChatSession session = new AiChatSession();
        session.setId(UUID.randomUUID().toString());
        session.setUserId(userId);
        session.setTitle(title);
        session.setGmtCreate(LocalDateTime.now());
        session.setGmtModified(LocalDateTime.now());

        List<AiChatSession> sessions = loadSessions(userId);
        sessions.add(0, session);
        persistSessions(userId, sessions);
        return session;
    }

    private synchronized AiChatMessage addMessageLocal(String sessionId, Long userId, String role, String content,
                                                       String reasoningContent,
                                                       List<ChatAttachment> attachments) {
        if (!ownsSession(userId, sessionId)) {
            throw new BusinessException("ai.chat.history.sessionNotOwned", new Object[]{sessionId});
        }
        AiChatMessage message = new AiChatMessage();
        message.setId(UUID.randomUUID().toString());
        message.setSessionId(sessionId);
        message.setRole(role);
        message.setContent(content);
        message.setReasoningContent(reasoningContent);
        if (attachments != null) {
            message.setAttachments(new ArrayList<>(attachments));
        }
        message.setGmtCreate(LocalDateTime.now());

        List<AiChatMessage> messages = loadMessages(sessionId);
        messages.add(message);
        persistMessages(sessionId, messages);
        touchSession(userId, sessionId);
        return message;
    }

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Re-fetch the session list for the current user before sending a message, and drop the stale sessionId from frontend state.
  2. If the session was deleted, create a new session via createSession first and use the returned sessionId.
  3. Catch BusinessException and surface a 'session not found' prompt offering to start a new chat.

Example fix

// before: reuse stale sessionId after delete
history.addMessage(deletedSessionId, userId, role, content);

// after: validate ownership client-side, recreate if missing
if (!sessionList.some(s => s.id === sessionId)) {
    const fresh = await createSession();
    sessionId = fresh.id;
}
history.addMessage(sessionId, userId, role, content);
Defensive patterns

Strategy: validation

Validate before calling

// client-side: ensure sessionId is in the current user's session list
if (!sessions.some(s => s.id === sessionId)) {
    sessionId = (await createSession()).id;
}
await history.addMessage(sessionId, role, content);

Try / catch

try {
    history.addMessage(sessionId, userId, role, content);
} catch (BusinessException e) {
    if ("ai.chat.history.sessionNotOwned".equals(e.getCode())) {
        // offer to start a new chat
        return ResponseEntity.status(403).body("session not found");
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling addMessage with a sessionId that does not belong to the current identityService user: a fabricated/guessed sessionId, a sessionId from another user, a sessionId that was deleted, or a stale sessionId held in the frontend after the session was removed.

Common situations: Frontend keeps a sessionId in state after the user deleted that session; a shared/edited URL carries an old sessionId; concurrent tabs where one deleted the session; an attempt to inject another user's sessionId. Note the in-memory sessions list is re-read per call from sessions-<userId>.json, so deletion is reflected immediately.

Related errors


AI-assisted analysis of OtterMind/Chat2DB@5ee1e990e7 (2026-08-14). Data as JSON: /api/errors/f5899fb0af146e6f. Report an issue: GitHub.