{"record":{"id":"5664d89dfede619d","repo":"OtterMind/Chat2DB","slug":"failed-to-load-ai-config-from","errorCode":null,"errorMessage":"Failed to load ai config from ","messagePattern":"Failed to load ai config from ","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"critical","filePath":"chat2db-community-server/chat2db-community-domain/chat2db-community-domain-core/src/main/java/ai/chat2db/community/domain/core/impl/ai/AiModelConfigServiceImpl.java","lineNumber":392,"sourceCode":"\n    private synchronized void loadFromDisk() {\n        if (!Files.exists(storagePath)) {\n            return;\n        }\n        try {\n            StorageData data = objectMapper.readValue(storagePath.toFile(), StorageData.class);\n            Map<Long, List<AiModelConfig>> loadedConfigMap = new HashMap<>();\n            if (CollectionUtils.isNotEmpty(data.getConfigs())) {\n                data.getConfigs().forEach(config -> {\n                    config.setApiKey(decryptApiKey(config.getApiKey()));\n                    Long userId = defaultValue(config.getUserId(), 0L);\n                    loadedConfigMap.computeIfAbsent(userId, key -> new ArrayList<>()).add(config);\n                });\n            }\n            userConfigMap.clear();\n            userConfigMap.putAll(loadedConfigMap);\n        } catch (Exception e) {\n            throw new IllegalStateException(\"Failed to load ai config from \" + storagePath, e);\n        }\n    }\n\n    private synchronized void persistToDisk() {\n        try {\n            Files.createDirectories(storagePath.getParent());\n            StorageData data = new StorageData();\n            List<AiModelConfig> all = userConfigMap.values().stream()\n                    .flatMap(List::stream)\n                    .map(this::encryptedCopy)\n                    .collect(Collectors.toList());\n            data.setConfigs(all);\n            objectMapper.writerWithDefaultPrettyPrinter().writeValue(storagePath.toFile(), data);\n        } catch (IOException e) {\n            throw new IllegalStateException(\"Failed to persist ai config to \" + storagePath, e);\n        }\n    }\n","sourceCodeStart":374,"sourceCodeEnd":410,"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/AiModelConfigServiceImpl.java#L374-L410","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Back up and remove the corrupt config store file so loadFromDisk starts empty (configs are then re-saved on next save).","Make persistToDisk atomic (write temp then ATOMIC_MOVE) to prevent half-written stores on crash.","Add @JsonIgnoreProperties(ignoreUnknown=true) to StorageData/AiModelConfig for forward-compatible deserialization across upgrades.","Inspect the wrapped exception cause to distinguish JSON corruption from an IO error."],"exampleFix":"// before: direct write, corruptible on crash\nobjectMapper.writerWithDefaultPrettyPrinter().writeValue(storagePath.toFile(), data);\n\n// after: atomic + tolerant\nobjectMapper.writerWithDefaultPrettyPrinter().writeValue(tmp.toFile(), data);\nFiles.move(tmp, storagePath, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);\n// and on the DTOs:\n@JsonIgnoreProperties(ignoreUnknown = true)\npublic class StorageData { ... }","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    aiConfigService = /* load */;\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Failed to load ai config from\")) {\n        Path store = extractPathFrom(e); // parse storagePath from message\n        backupAndQuarantine(store);      // move aside the corrupt store\n        aiConfigService = /* retry load with empty store */;\n    } else throw e;\n}","preventionTips":["Make persistToDisk atomic (temp + ATOMIC_MOVE) to prevent corrupt stores on crash.","Add @JsonIgnoreProperties(ignoreUnknown=true) to StorageData/AiModelConfig for upgrade safety.","Back up the corrupt config store before discarding it; inspect the wrapped cause."],"tags":["ai","model-config","startup","serialization","data-corruption"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}