{"record":{"id":"f5899fb0af146e6f","repo":"OtterMind/Chat2DB","slug":"ai-chat-history-sessionnotowned","errorCode":"ai.chat.history.sessionNotOwned","errorMessage":"ai.chat.history.sessionNotOwned","messagePattern":"ai\\.chat\\.history\\.sessionNotOwned","errorType":"exception","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiChatHistoryServiceImpl.java","lineNumber":121,"sourceCode":"    private synchronized AiChatSession createSessionLocal(Long userId, String title) {\n        AiChatSession session = new AiChatSession();\n        session.setId(UUID.randomUUID().toString());\n        session.setUserId(userId);\n        session.setTitle(title);\n        session.setGmtCreate(LocalDateTime.now());\n        session.setGmtModified(LocalDateTime.now());\n\n        List<AiChatSession> sessions = loadSessions(userId);\n        sessions.add(0, session);\n        persistSessions(userId, sessions);\n        return session;\n    }\n\n    private synchronized AiChatMessage addMessageLocal(String sessionId, Long userId, String role, String content,\n                                                       String reasoningContent,\n                                                       List<ChatAttachment> attachments) {\n        if (!ownsSession(userId, sessionId)) {\n            throw new BusinessException(\"ai.chat.history.sessionNotOwned\", new Object[]{sessionId});\n        }\n        AiChatMessage message = new AiChatMessage();\n        message.setId(UUID.randomUUID().toString());\n        message.setSessionId(sessionId);\n        message.setRole(role);\n        message.setContent(content);\n        message.setReasoningContent(reasoningContent);\n        if (attachments != null) {\n            message.setAttachments(new ArrayList<>(attachments));\n        }\n        message.setGmtCreate(LocalDateTime.now());\n\n        List<AiChatMessage> messages = loadMessages(sessionId);\n        messages.add(message);\n        persistMessages(sessionId, messages);\n        touchSession(userId, sessionId);\n        return message;\n    }","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/OtterMind/Chat2DB/blob/5ee1e990e73fbcae1969dc554be254fedb3ab888/chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiChatHistoryServiceImpl.java#L103-L139","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-fetch the session list for the current user before sending a message, and drop the stale sessionId from frontend state.","If the session was deleted, create a new session via createSession first and use the returned sessionId.","Catch BusinessException and surface a 'session not found' prompt offering to start a new chat."],"exampleFix":"// before: reuse stale sessionId after delete\nhistory.addMessage(deletedSessionId, userId, role, content);\n\n// after: validate ownership client-side, recreate if missing\nif (!sessionList.some(s => s.id === sessionId)) {\n    const fresh = await createSession();\n    sessionId = fresh.id;\n}\nhistory.addMessage(sessionId, userId, role, content);","handlingStrategy":"validation","validationCode":"// client-side: ensure sessionId is in the current user's session list\nif (!sessions.some(s => s.id === sessionId)) {\n    sessionId = (await createSession()).id;\n}\nawait history.addMessage(sessionId, role, content);","typeGuard":null,"tryCatchPattern":"try {\n    history.addMessage(sessionId, userId, role, content);\n} catch (BusinessException e) {\n    if (\"ai.chat.history.sessionNotOwned\".equals(e.getCode())) {\n        // offer to start a new chat\n        return ResponseEntity.status(403).body(\"session not found\");\n    }\n    throw e;\n}","preventionTips":["Re-fetch the session list before sending after a delete or long idle.","Clear stale sessionIds from frontend state on session deletion.","Do not pass sessionIds from URLs or other users."],"tags":["ai","chat-history","authorization","ownership","validation"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}