OtterMind/Chat2DB · error · BusinessException

ai.chat.history.persistMessagesFailed

ai.chat.history.persistMessagesFailed

Error message

ai.chat.history.persistMessagesFailed

What it means

Thrown by AiChatHistoryServiceImpl.persistMessages in the catch(IOException) while writing <sessionId>.json (parent dir creation or serialization). Args are {path, e.getMessage()}. Any write failure aborts persistence of that session's messages, so an in-flight conversation may not be saved.

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:240

            return new ArrayList<>();
        }
        try {
            MessagesFile file = objectMapper.readValue(path.toFile(), MessagesFile.class);
            return file.getMessages() != null ? file.getMessages() : new ArrayList<>();
        } catch (Exception e) {
            throw new BusinessException("ai.chat.history.loadMessagesFailed", new Object[]{path, e.getMessage()}, e);
        }
    }

    private void persistMessages(String sessionId, List<AiChatMessage> messages) {
        Path path = messagesPath(sessionId);
        try {
            Files.createDirectories(path.getParent());
            MessagesFile file = new MessagesFile();
            file.setMessages(messages);
            objectMapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), file);
        } catch (IOException e) {
            throw new BusinessException("ai.chat.history.persistMessagesFailed", new Object[]{path, e.getMessage()}, e);
        }
    }

    private void touchSession(Long userId, String sessionId) {
        List<AiChatSession> sessions = loadSessions(userId);
        sessions.stream()
                .filter(s -> Objects.equals(s.getId(), sessionId))
                .findFirst()
                .ifPresent(s -> s.setGmtModified(LocalDateTime.now()));
        persistSessions(userId, sessions);
    }

    @Data
    public static class SessionsFile {
        private List<AiChatSession> sessions = new ArrayList<>();
    }

    @Data

View on GitHub (pinned to 5ee1e990e7)

Solutions

  1. Confirm read+write+execute permission on <basePath>/ai-chat-history for the process user and free space/inodes.
  2. Point chat2db.base.path at a writable volume.
  3. Use an atomic write (temp + ATOMIC_MOVE) so partial writes never replace a good file on a transient failure.
  4. Surface the failure to the user so they know the last message was not persisted.

Example fix

// before: direct write, partial on failure
objectMapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), file);

// after: atomic, and notify on failure
try {
    Path tmp = path.resolveSibling(path.getFileName() + ".tmp");
    objectMapper.writerWithDefaultPrettyPrinter().writeValue(tmp.toFile(), file);
    Files.move(tmp, path, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
    notifyUser("message could not be saved");
    throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

Path dir = Paths.get(ConfigUtils.getBasePath(), "ai-chat-history");
if (!Files.isWritable(dir)) {
    throw new IllegalStateException("ai-chat-history not writable");
}

Try / catch

try {
    history.addMessage(...);
} catch (BusinessException e) {
    if ("ai.chat.history.persistMessagesFailed".equals(e.getCode())) {
        notifyUser("your last message could not be saved; retry");
        return ResponseEntity.status(507).build();
    }
    throw e;
}

Prevention

When it happens

Trigger: Files.createDirectories(parent) fails or Jackson writeValue throws IOException: disk full, quota exceeded, read-only mount, permission denied, or the parent dir was removed during the call.

Common situations: Disk full mid-conversation; the process user lost write permission on ai-chat-history; read-only container volume; inode exhaustion.

Related errors


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