From afe8291423060a7f4101378580b621c58b713cc8 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 1 Dec 2025 01:10:14 +0100 Subject: [PATCH 1/2] fix: exit on invalid configuration and add bypass validation warning --- .../services/Config/loadCustomConfig.js | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/api/server/services/Config/loadCustomConfig.js b/api/server/services/Config/loadCustomConfig.js index c0415674b902..db2504995727 100644 --- a/api/server/services/Config/loadCustomConfig.js +++ b/api/server/services/Config/loadCustomConfig.js @@ -85,26 +85,32 @@ Please specify a correct \`imageOutputType\` value (case-sensitive). let errorMessage = `Invalid custom config file at ${configPath}: ${JSON.stringify(result.error, null, 2)}`; - if (i === 0) { - logger.error(errorMessage); - const speechError = result.error.errors.find( - (err) => - err.code === 'unrecognized_keys' && - (err.message?.includes('stt') || err.message?.includes('tts')), - ); + logger.error(errorMessage); + const speechError = result.error.errors.find( + (err) => + err.code === 'unrecognized_keys' && + (err.message?.includes('stt') || err.message?.includes('tts')), + ); - if (speechError) { - logger.warn(` + if (speechError) { + logger.warn(` The Speech-to-text and Text-to-speech configuration format has recently changed. If you're getting this error, please refer to the latest documentation: https://www.librechat.ai/docs/configuration/stt_tts`); - } + } - i++; + if (process.env.CONFIG_BYPASS_VALIDATION === 'true') { + logger.warn( + 'CONFIG_BYPASS_VALIDATION is enabled. Continuing with default configuration despite validation errors.', + ); + return null; } - return null; + logger.error( + 'Exiting due to invalid configuration. Set CONFIG_BYPASS_VALIDATION=true to bypass this check.', + ); + process.exit(1); } else { if (printConfig) { logger.info('Custom config file loaded:'); From 47ee3cd8669f2aff6a17c3a3be9f0affdc6b8436 Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:13:33 +0100 Subject: [PATCH 2/2] test: enhance loadCustomConfig tests for exit behavior on invalid config --- .../services/Config/loadCustomConfig.spec.js | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/api/server/services/Config/loadCustomConfig.spec.js b/api/server/services/Config/loadCustomConfig.spec.js index 4f2006a0531f..f7f11dc8f6c2 100644 --- a/api/server/services/Config/loadCustomConfig.spec.js +++ b/api/server/services/Config/loadCustomConfig.spec.js @@ -50,8 +50,25 @@ const { logger } = require('@librechat/data-schemas'); const loadCustomConfig = require('./loadCustomConfig'); describe('loadCustomConfig', () => { + const originalExit = process.exit; + const mockExit = jest.fn((code) => { + throw new Error(`process.exit called with "${code}"`); + }); + + beforeAll(() => { + process.exit = mockExit; + }); + + afterAll(() => { + process.exit = originalExit; + }); + beforeEach(() => { jest.resetAllMocks(); + // Re-apply the exit mock implementation after resetAllMocks + mockExit.mockImplementation((code) => { + throw new Error(`process.exit called with "${code}"`); + }); delete process.env.CONFIG_PATH; }); @@ -94,20 +111,38 @@ describe('loadCustomConfig', () => { it('should return null and log if config schema validation fails', async () => { const invalidConfig = { invalidField: true }; process.env.CONFIG_PATH = 'invalidConfig.yaml'; + process.env.CONFIG_BYPASS_VALIDATION = 'true'; loadYaml.mockReturnValueOnce(invalidConfig); const result = await loadCustomConfig(); expect(result).toBeNull(); + expect(logger.warn).toHaveBeenCalledWith( + 'CONFIG_BYPASS_VALIDATION is enabled. Continuing with default configuration despite validation errors.', + ); + delete process.env.CONFIG_BYPASS_VALIDATION; + }); + + it('should call process.exit(1) when config validation fails without bypass', async () => { + const invalidConfig = { invalidField: true }; + process.env.CONFIG_PATH = 'invalidConfig.yaml'; + loadYaml.mockReturnValueOnce(invalidConfig); + + await expect(loadCustomConfig()).rejects.toThrow('process.exit called with "1"'); + expect(logger.error).toHaveBeenCalledWith( + 'Exiting due to invalid configuration. Set CONFIG_BYPASS_VALIDATION=true to bypass this check.', + ); }); it('should handle and return null on YAML parse error for a string response from remote', async () => { process.env.CONFIG_PATH = 'http://example.com/config.yaml'; + process.env.CONFIG_BYPASS_VALIDATION = 'true'; axios.get.mockResolvedValue({ data: 'invalidYAMLContent' }); const result = await loadCustomConfig(); expect(result).toBeNull(); + delete process.env.CONFIG_BYPASS_VALIDATION; }); it('should return the custom config object for a valid remote config file', async () => {