OtterMind/Chat2DB · critical · IllegalStateException

Failed to load ai config from

Error message

Failed to load ai config from 

What it means

Thrown as IllegalStateException('Failed to load ai config from ' + storagePath) by AiModelConfigServiceImpl.loadFromDisk in the catch(Exception) when reading the persisted AI config JSON fails. This runs at construction/startup, so a corrupt config store can block the AI subsystem from initializing. The wrapped exception carries the root cause (parse error or IOException).

Solutions

  1. Back up and remove the corrupt config store file so loadFromDisk starts empty (configs are then re-saved on next save).
  2. Make persistToDisk atomic (write temp then ATOMIC_MOVE) to prevent half-written stores on crash.
  3. Add @JsonIgnoreProperties(ignoreUnknown=true) to StorageData/AiModelConfig for forward-compatible deserialization across upgrades.
  4. Inspect the wrapped exception cause to distinguish JSON corruption from an IO error.

Example fix

// before: direct write, corruptible on crash
objectMapper.writerWithDefaultPrettyPrinter().writeValue(storagePath.toFile(), data);

// after: atomic + tolerant
objectMapper.writerWithDefaultPrettyPrinter().writeValue(tmp.toFile(), data);
Files.move(tmp, storagePath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
// and on the DTOs:
@JsonIgnoreProperties(ignoreUnknown = true)
public class StorageData { ... }
Defensive patterns

Strategy: try-catch

Try / catch

try {
    aiConfigService = /* load */;
} catch (IllegalStateException e) {
    if (e.getMessage() != null && e.getMessage().startsWith("Failed to load ai config from")) {
        Path store = extractPathFrom(e); // parse storagePath from message
        backupAndQuarantine(store);      // move aside the corrupt store
        aiConfigService = /* retry load with empty store */;
    } else throw e;
}

Prevention

When it happens

Trigger: The ai config storage file (storagePath, the persisted model-config store) exists but objectMapper.readValue cannot deserialize it: truncated/corrupt JSON from a crashed persistToDisk, a schema change to StorageData/AiModelConfig the stored file does not match, or an IOException reading the file.

Common situations: Process was killed during persistToDisk leaving a half-written config file; the file was manually edited; an upgrade changed AiModelConfig fields (e.g. a new required field) so stored configs no longer bind; the config store is on a failing/unmounted volume.

Related errors


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

Appendix: source

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

    private synchronized void loadFromDisk() {
        if (!Files.exists(storagePath)) {
            return;
        }
        try {
            StorageData data = objectMapper.readValue(storagePath.toFile(), StorageData.class);
            Map<Long, List<AiModelConfig>> loadedConfigMap = new HashMap<>();
            if (CollectionUtils.isNotEmpty(data.getConfigs())) {
                data.getConfigs().forEach(config -> {
                    config.setApiKey(decryptApiKey(config.getApiKey()));
                    Long userId = defaultValue(config.getUserId(), 0L);
                    loadedConfigMap.computeIfAbsent(userId, key -> new ArrayList<>()).add(config);
                });
            }
            userConfigMap.clear();
            userConfigMap.putAll(loadedConfigMap);
        } catch (Exception e) {
            throw new IllegalStateException("Failed to load ai config from " + storagePath, e);
        }
    }

    private synchronized void persistToDisk() {
        try {
            Files.createDirectories(storagePath.getParent());
            StorageData data = new StorageData();
            List<AiModelConfig> all = userConfigMap.values().stream()
                    .flatMap(List::stream)
                    .map(this::encryptedCopy)
                    .collect(Collectors.toList());
            data.setConfigs(all);
            objectMapper.writerWithDefaultPrettyPrinter().writeValue(storagePath.toFile(), data);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to persist ai config to " + storagePath, e);
        }
    }

View on GitHub (pinned to 5ee1e990e7)