{"record":{"id":"ad860d6ac1a23f4d","repo":"OtterMind/Chat2DB","slug":"failed-to-persist-ai-config-to","errorCode":null,"errorMessage":"Failed to persist ai config to ","messagePattern":"Failed to persist ai config to ","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":407,"sourceCode":"            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\n    private AiModelConfig encryptedCopy(AiModelConfig config) {\n        AiModelConfig copy = new AiModelConfig();\n        BeanUtils.copyProperties(config, copy);\n        copy.setApiKey(encryptApiKey(config.getApiKey()));\n        return copy;\n    }\n\n    private String encryptApiKey(String apiKey) {\n        if (aesGcmUtil == null || apiKey == null || apiKey.isEmpty()) {\n            return apiKey;\n        }\n        return aesGcmUtil.encryptAiModelApiKey(apiKey);\n    }\n\n    private String decryptApiKey(String storedApiKey) {","sourceCodeStart":389,"sourceCodeEnd":425,"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#L389-L425","documentation":"Thrown by persistToDisk() when Jackson/object-mapper file write of the AI model config (StorageData, with encrypted API keys) to storagePath raises an IOException. It is wrapped in an IllegalStateException so the original I/O cause is preserved as the cause. The method is synchronized, so this is the terminal failure after every successful in-memory config mutation (save/delete).","triggerScenarios":"Any call that mutates AI model config (create/update/delete config) which then calls persistToDisk(); the write fails because the parent directory is not writable, the disk is full, the path is read-only, or another process holds an exclusive lock on the file.","commonSituations":"Desktop/JCEF package installed to Program Files without write privileges; storagePath resolving to a read-only install dir; full disk; container running as a UID that cannot write the configured config dir; path on a network mount that dropped.","solutions":["Check the exception message for the exact storagePath and verify the process has write permission on its parent (ls -ld on the dir, chmod/chown as needed).","Confirm free disk space and that the path is on a writable filesystem (not a read-only install dir).","If running the desktop package from a system-protected location, move/configure the storage path to a user-writable directory (e.g. user home config dir).","Ensure no second Chat2DB instance or external process locks the file; restart the process after fixing permissions."],"exampleFix":"// before: storagePath resolved under a read-only install dir\nPath storagePath = appDir.resolve(\"ai-config.json\");\n\n// after: resolve under a user-writable config directory\nPath storagePath = Paths.get(System.getProperty(\"user.home\"), \".chat2db\", \"ai-config.json\");","handlingStrategy":"try-catch","validationCode":"Path parent = storagePath.getParent();\nif (!Files.isWritable(parent)) {\n    throw new IllegalStateException(\"Config dir not writable: \" + parent);\n}\nif (parent.toFile().getUsableSpace() < 1024L) {\n    throw new IllegalStateException(\"Insufficient disk space for config write\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    aiModelConfigService.save(config);\n} catch (IllegalStateException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Failed to persist ai config to \")) {\n        log.error(\"AI config persistence failed for path; check permissions/disk\", e);\n        // surface to user without losing in-memory state\n    } else {\n        throw e;\n    }\n}","preventionTips":["Resolve the AI config storage path under a user-writable directory at install time.","Run a startup self-check: attempt to create+delete a temp file in the config dir.","Run the process under a user that owns the config directory.","Monitor free disk space on the volume hosting the config file."],"tags":["io","filesystem","ai-config","permissions","serialization"],"backgroundTag":null,"analyzedSha":"5ee1e990e73fbcae1969dc554be254fedb3ab888","analyzedAt":"2026-08-14T07:05:03.077Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}