{"record":{"id":"15deb026c03ebc80","repo":"OtterMind/Chat2DB","slug":"ai-chat-history-loadmessagesfailed","errorCode":"ai.chat.history.loadMessagesFailed","errorMessage":"ai.chat.history.loadMessagesFailed","messagePattern":"ai\\.chat\\.history\\.loadMessagesFailed","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":228,"sourceCode":"            Files.createDirectories(path.getParent());\n            SessionsFile file = new SessionsFile();\n            file.setSessions(sessions);\n            objectMapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), file);\n        } catch (IOException e) {\n            throw new BusinessException(\"ai.chat.history.persistSessionsFailed\", new Object[]{path, e.getMessage()}, e);\n        }\n    }\n\n    private List<AiChatMessage> loadMessages(String sessionId) {\n        Path path = messagesPath(sessionId);\n        if (!Files.exists(path)) {\n            return new ArrayList<>();\n        }\n        try {\n            MessagesFile file = objectMapper.readValue(path.toFile(), MessagesFile.class);\n            return file.getMessages() != null ? file.getMessages() : new ArrayList<>();\n        } catch (Exception e) {\n            throw new BusinessException(\"ai.chat.history.loadMessagesFailed\", new Object[]{path, e.getMessage()}, e);\n        }\n    }\n\n    private void persistMessages(String sessionId, List<AiChatMessage> messages) {\n        Path path = messagesPath(sessionId);\n        try {\n            Files.createDirectories(path.getParent());\n            MessagesFile file = new MessagesFile();\n            file.setMessages(messages);\n            objectMapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), file);\n        } catch (IOException e) {\n            throw new BusinessException(\"ai.chat.history.persistMessagesFailed\", new Object[]{path, e.getMessage()}, e);\n        }\n    }\n\n    private void touchSession(Long userId, String sessionId) {\n        List<AiChatSession> sessions = loadSessions(userId);\n        sessions.stream()","sourceCodeStart":210,"sourceCodeEnd":246,"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#L210-L246","documentation":"Thrown by AiChatHistoryServiceImpl.loadMessages in the catch(Exception) around objectMapper.readValue of <sessionId>.json. Args are {path, e.getMessage()}. Mirrors the sessions load failure but for a single session's message list; any JSON/IO exception aborts message loading for that session.","triggerScenarios":"The file <basePath>/ai-chat-history/<sessionId>.json exists but is corrupt or unreadable: truncated JSON from a crashed persistMessages, schema drift after an upgrade, or an IOException reading the file. Unlike sessions, a missing messages file is tolerated (returns empty list) - only an existing-but-unreadable file throws.","commonSituations":"Process killed mid-write of messages leaving a half file; manual edit; Jackson cannot bind AiChatMessage after a field change; storage corruption.","solutions":["Back up and remove the corrupt <sessionId>.json so the session starts with an empty message history.","Make persistMessages atomic (temp file + ATOMIC_MOVE) to prevent half-written files on crash.","Add @JsonIgnoreProperties(ignoreUnknown=true) to AiChatMessage for forward-compatible deserialization after upgrades."],"exampleFix":"// before: non-atomic message write\nobjectMapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), file);\n\n// after: atomic temp+move\nPath tmp = path.resolveSibling(path.getFileName() + \".tmp\");\nobjectMapper.writerWithDefaultPrettyPrinter().writeValue(tmp.toFile(), file);\nFiles.move(tmp, path, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    history.listMessages(sessionId);\n} catch (BusinessException e) {\n    if (\"ai.chat.history.loadMessagesFailed\".equals(e.getCode())) {\n        Path p = (Path) e.getArgs()[0];\n        backupAndQuarantine(p);\n        return List.of(); // empty history for this session\n    }\n    throw e;\n}","preventionTips":["Write messages atomically (temp + ATOMIC_MOVE) so a crash cannot corrupt them.","Add @JsonIgnoreProperties(ignoreUnknown=true) to AiChatMessage for forward compatibility.","Quarantine rather than delete corrupt message files so history can be recovered."],"tags":["ai","chat-history","serialization","io","data-corruption"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}