From 24ec1f66b35d9f6bcbf8cf78cb4e4560b97cd5cb Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 6 Dec 2025 18:41:19 +0100 Subject: [PATCH] fix: skip repo clone when config is missing, onboarding is disabled, and requireConfig is required --- docs/usage/self-hosted-configuration.md | 4 ++ lib/workers/repository/init/apis.spec.ts | 81 ++++++++++++++++++++++++ lib/workers/repository/init/apis.ts | 14 ++++ 3 files changed, 99 insertions(+) diff --git a/docs/usage/self-hosted-configuration.md b/docs/usage/self-hosted-configuration.md index e03109d2183..0b7c2a1c108 100644 --- a/docs/usage/self-hosted-configuration.md +++ b/docs/usage/self-hosted-configuration.md @@ -1095,6 +1095,10 @@ In that case, Renovate searches the repository config file for any of these conf If Renovate finds any of the above configurations, it continues initializing the repository. If not, then Renovate skips the repository without cloning it. +A third use case is when `onboarding` is set to `false` and `requireConfig` is not `"optional"` or `"ignored"`. +In this scenario, if the config file is missing, Renovate will skip the repository without cloning it. +This is useful for large organizations with many repositories where only some have Renovate enabled. + ## password ## persistRepoData diff --git a/lib/workers/repository/init/apis.spec.ts b/lib/workers/repository/init/apis.spec.ts index 5743dafc286..2d2f0de1c67 100644 --- a/lib/workers/repository/init/apis.spec.ts +++ b/lib/workers/repository/init/apis.spec.ts @@ -2,6 +2,7 @@ import { getConfig } from '../../../config/defaults'; import { REPOSITORY_DISABLED, REPOSITORY_FORKED, + REPOSITORY_NO_CONFIG, } from '../../../constants/error-messages'; import { initApis } from './apis'; import { platform } from '~test/util'; @@ -220,5 +221,85 @@ describe('workers/repository/init/apis', () => { }), ).rejects.toThrow(REPOSITORY_DISABLED); }); + + it('skips without cloning when config is missing and onboarding is disabled', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: '123', + }); + platform.getJsonFile.mockResolvedValueOnce(null); + await expect( + initApis({ + ...config, + optimizeForDisabled: true, + onboarding: false, + }), + ).rejects.toThrow(REPOSITORY_NO_CONFIG); + }); + + it('skips without cloning when config is missing, onboarding is disabled, and requireConfig is required', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: '123', + }); + platform.getJsonFile.mockResolvedValueOnce(null); + await expect( + initApis({ + ...config, + optimizeForDisabled: true, + onboarding: false, + requireConfig: 'required', + }), + ).rejects.toThrow(REPOSITORY_NO_CONFIG); + }); + + it('continues when config is missing, onboarding is disabled, but requireConfig is optional', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: '123', + }); + platform.getJsonFile.mockResolvedValueOnce(null); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboarding: false, + requireConfig: 'optional', + }); + expect(workerPlatformConfig).toBeTruthy(); + }); + + it('continues when config is missing, onboarding is disabled, but requireConfig is ignored', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: '123', + }); + platform.getJsonFile.mockResolvedValueOnce(null); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboarding: false, + requireConfig: 'ignored', + }); + expect(workerPlatformConfig).toBeTruthy(); + }); + + it('continues when config exists even with onboarding disabled', async () => { + platform.initRepo.mockResolvedValueOnce({ + defaultBranch: 'master', + isFork: false, + repoFingerprint: '123', + }); + platform.getJsonFile.mockResolvedValueOnce({ extends: ['config:base'] }); + const workerPlatformConfig = await initApis({ + ...config, + optimizeForDisabled: true, + onboarding: false, + }); + expect(workerPlatformConfig).toBeTruthy(); + }); }); }); diff --git a/lib/workers/repository/init/apis.ts b/lib/workers/repository/init/apis.ts index 4313f409906..adae1c46183 100644 --- a/lib/workers/repository/init/apis.ts +++ b/lib/workers/repository/init/apis.ts @@ -2,6 +2,7 @@ import type { RenovateConfig } from '../../../config/types'; import { REPOSITORY_DISABLED_BY_CONFIG, REPOSITORY_FORKED, + REPOSITORY_NO_CONFIG, } from '../../../constants/error-messages'; import { logger } from '../../../logger'; import type { RepoParams, RepoResult } from '../../../modules/platform'; @@ -29,6 +30,19 @@ async function validateOptimizeForDisabled( if (renovateConfig?.enabled === false) { throw new Error(REPOSITORY_DISABLED_BY_CONFIG); } + // If config file is missing, onboarding is disabled, and config is required, + // skip the repo early without cloning + if ( + !renovateConfig && + config.onboarding === false && + config.requireConfig !== 'optional' && + config.requireConfig !== 'ignored' + ) { + logger.debug( + 'Repository has no config and onboarding is disabled - skipping', + ); + throw new Error(REPOSITORY_NO_CONFIG); + } /* * The following is to support a use case within Mend customers where: * - Bot admins configure install the bot into every repo