diff --git a/lib/config/common.ts b/lib/config/common.ts
index dd60f538c24fbe378211a6349cb483f9e758463c..4348a5d319680dd45b15ef1a9d628b6bf1548d31 100644
--- a/lib/config/common.ts
+++ b/lib/config/common.ts
@@ -52,7 +52,7 @@ export interface RenovateSharedConfig {
   repositoryCache?: RepositoryCacheConfig;
   requiredStatusChecks?: string[];
   schedule?: string[];
-  semanticCommits?: boolean;
+  semanticCommits?: 'auto' | 'enabled' | 'disabled';
   semanticCommitScope?: string;
   semanticCommitType?: string;
   suppressNotifications?: string[];
diff --git a/lib/config/definitions.ts b/lib/config/definitions.ts
index 6e0338c7a81f9e3137b193426d44f92a77482e8f..ed3cfe4617dc713b861c96be8b0f54c397701f71 100644
--- a/lib/config/definitions.ts
+++ b/lib/config/definitions.ts
@@ -1084,8 +1084,9 @@ const options: RenovateOptions[] = [
   {
     name: 'semanticCommits',
     description: 'Enable semantic commit prefixes for commits and PR titles',
-    type: 'boolean',
-    default: null,
+    type: 'string',
+    allowedValues: ['auto', 'enabled', 'disabled'],
+    default: 'auto',
   },
   {
     name: 'semanticCommitType',
diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts
index b81057b142c8dd7c10edf13cd416606964529634..3ff1e01cef65ebb77fabbd4ebf6e28d7feb45d4f 100644
--- a/lib/config/migration.spec.ts
+++ b/lib/config/migration.spec.ts
@@ -1,6 +1,7 @@
 import { PLATFORM_TYPE_GITHUB } from '../constants/platforms';
 import { getConfig } from './defaults';
 import * as configMigration from './migration';
+import { MigratedConfig } from './migration';
 import { RenovateSharedConfig, RenovateConfig as _RenovateConfig } from '.';
 
 const defaultConfig = getConfig();
@@ -129,7 +130,7 @@ describe('config/migration', () => {
         ],
         raiseDeprecationWarnings: false,
       };
-      const parentConfig = { ...defaultConfig, semanticCommits: false };
+      const parentConfig = { ...defaultConfig, semanticCommits: 'disabled' };
       const { isMigrated, migratedConfig } = configMigration.migrateConfig(
         config,
         parentConfig
@@ -269,7 +270,6 @@ describe('config/migration', () => {
     it('does not migrate config', () => {
       const config: RenovateConfig = {
         enabled: true,
-        semanticCommits: true,
         separateMinorPatch: true,
       };
       const { isMigrated, migratedConfig } = configMigration.migrateConfig(
@@ -403,5 +403,24 @@ describe('config/migration', () => {
       expect(migratedConfig).toMatchSnapshot();
       expect(isMigrated).toBe(true);
     });
+    it('it migrates semanticCommits', () => {
+      let config: RenovateConfig;
+      let res: MigratedConfig;
+
+      config = { semanticCommits: true as never };
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({ semanticCommits: 'enabled' });
+
+      config = { semanticCommits: false as never };
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({ semanticCommits: 'disabled' });
+
+      config = { semanticCommits: null as never };
+      res = configMigration.migrateConfig(config);
+      expect(res.isMigrated).toBe(true);
+      expect(res.migratedConfig).toMatchObject({ semanticCommits: 'auto' });
+    });
   });
 });
diff --git a/lib/config/migration.ts b/lib/config/migration.ts
index e1aa1ab235dfd1aba02bb38f2bc87ae609a34ed1..9791fff3229dfbb538668b37a4309954df5b0690 100644
--- a/lib/config/migration.ts
+++ b/lib/config/migration.ts
@@ -101,6 +101,15 @@ export function migrateConfig(
           migratedConfig.postUpdateOptions.push('gomodTidy');
         }
         delete migratedConfig.gomodTidy;
+      } else if (key === 'semanticCommits') {
+        isMigrated = true;
+        if (val === true) {
+          migratedConfig.semanticCommits = 'enabled';
+        } else if (val === false) {
+          migratedConfig.semanticCommits = 'disabled';
+        } else {
+          migratedConfig.semanticCommits = 'auto';
+        }
       } else if (parentKey === 'hostRules' && key === 'platform') {
         isMigrated = true;
         migratedConfig.hostType = val;
diff --git a/lib/config/presets/__snapshots__/index.spec.ts.snap b/lib/config/presets/__snapshots__/index.spec.ts.snap
index 2156450ef059c8dc713c2e3578c1949843a01b18..f96377310cc9c43debfecb29cdb9631707d4ee95 100644
--- a/lib/config/presets/__snapshots__/index.spec.ts.snap
+++ b/lib/config/presets/__snapshots__/index.spec.ts.snap
@@ -393,7 +393,7 @@ Object {
   "schedule": Array [
     "before 8am",
   ],
-  "semanticCommits": true,
+  "semanticCommits": "enabled",
   "separateMajorMinor": true,
   "separateMinorPatch": false,
   "timezone": "Asia/Taipei",
diff --git a/lib/config/presets/internal/default.ts b/lib/config/presets/internal/default.ts
index f45ba82d608de502e378deebcb00515b2ce4079f..dbdc507686aaf8e22f26be410fe0d6aaf8ee55a1 100644
--- a/lib/config/presets/internal/default.ts
+++ b/lib/config/presets/internal/default.ts
@@ -510,11 +510,11 @@ export const presets: Record<string, Preset> = {
   },
   semanticCommits: {
     description: 'Use semantic prefixes for commit messages and PR titles',
-    semanticCommits: true,
+    semanticCommits: 'enabled',
   },
   semanticCommitsDisabled: {
     description: 'Disable semantic prefixes for commit messages and PR titles',
-    semanticCommits: false,
+    semanticCommits: 'disabled',
   },
   disableLockFiles: {
     description: 'Disable lock file updates',
diff --git a/lib/workers/branch/commit.spec.ts b/lib/workers/branch/commit.spec.ts
index 65add63e6f921604314e17eecca44cd327325094..bd559cb48d44a73be3b6ae636481aa10992492e9 100644
--- a/lib/workers/branch/commit.spec.ts
+++ b/lib/workers/branch/commit.spec.ts
@@ -12,7 +12,7 @@ describe('workers/branch/automerge', () => {
         ...defaultConfig,
         branchName: 'renovate/some-branch',
         commitMessage: 'some commit message',
-        semanticCommits: false,
+        semanticCommits: 'disabled',
         semanticCommitType: 'a',
         semanticCommitScope: 'b',
         updatedPackageFiles: [],
diff --git a/lib/workers/repository/init/config.ts b/lib/workers/repository/init/config.ts
index 0422ae6acf3fa985360bc40e47033c4bffbf04fe..c59dfd80f201106d812469b1ea2ef3a8e4293d28 100644
--- a/lib/workers/repository/init/config.ts
+++ b/lib/workers/repository/init/config.ts
@@ -222,7 +222,8 @@ export async function getRepoConfig(
   config.baseBranch = config.defaultBranch;
   config = await checkOnboardingBranch(config);
   config = await mergeRenovateConfig(config);
-  config.semanticCommits =
-    config.semanticCommits ?? (await detectSemanticCommits());
+  if (config.semanticCommits === 'auto') {
+    config.semanticCommits = await detectSemanticCommits();
+  }
   return config;
 }
diff --git a/lib/workers/repository/init/semantic.spec.ts b/lib/workers/repository/init/semantic.spec.ts
index 433a21d5254c1aebcc990dea24244b9a5ff48a90..0a5b37fdd8e10286ba672a2813605065ce93dd81 100644
--- a/lib/workers/repository/init/semantic.spec.ts
+++ b/lib/workers/repository/init/semantic.spec.ts
@@ -17,13 +17,13 @@ describe('workers/repository/init/semantic', () => {
       config.semanticCommits = null;
       git.getCommitMessages.mockResolvedValue(['foo', 'bar']);
       const res = await detectSemanticCommits();
-      expect(res).toBe(false);
+      expect(res).toBe('disabled');
     });
     it('detects true if known', async () => {
       config.semanticCommits = null;
       git.getCommitMessages.mockResolvedValue(['fix: foo', 'refactor: bar']);
       const res = await detectSemanticCommits();
-      expect(res).toBe(true);
+      expect(res).toBe('enabled');
     });
   });
 });
diff --git a/lib/workers/repository/init/semantic.ts b/lib/workers/repository/init/semantic.ts
index f9b74b62fd4eb12a328d8530edfe90e75d454f2b..62816e63e563411d47a01ea6ae525534b4743f0b 100644
--- a/lib/workers/repository/init/semantic.ts
+++ b/lib/workers/repository/init/semantic.ts
@@ -2,7 +2,9 @@ import conventionalCommitsDetector from 'conventional-commits-detector';
 import { logger } from '../../../logger';
 import { getCommitMessages } from '../../../util/git';
 
-export async function detectSemanticCommits(): Promise<boolean> {
+type DetectedSemanticCommit = 'enabled' | 'disabled';
+
+export async function detectSemanticCommits(): Promise<DetectedSemanticCommit> {
   logger.debug('detectSemanticCommits()');
   const commitMessages = await getCommitMessages();
   logger.trace(`commitMessages=${JSON.stringify(commitMessages)}`);
@@ -10,8 +12,8 @@ export async function detectSemanticCommits(): Promise<boolean> {
   logger.debug('Semantic commits detection: ' + type);
   if (type === 'angular') {
     logger.debug('angular semantic commits detected');
-    return true;
+    return 'enabled';
   }
   logger.debug('No semantic commits detected');
-  return false;
+  return 'disabled';
 }
diff --git a/lib/workers/repository/onboarding/branch/create.spec.ts b/lib/workers/repository/onboarding/branch/create.spec.ts
index 282a88965b1a0b9a1212159351d5307cd7c72820..410f42bcc43c87e6abc72fdb5549d23d23cc4427 100644
--- a/lib/workers/repository/onboarding/branch/create.spec.ts
+++ b/lib/workers/repository/onboarding/branch/create.spec.ts
@@ -72,7 +72,7 @@ describe('workers/repository/onboarding/branch', () => {
     describe('applies semanticCommit prefix', () => {
       it('to the default commit message', async () => {
         const prefix = 'chore(deps)';
-        config.semanticCommits = true;
+        config.semanticCommits = 'enabled';
         await createOnboardingBranch(config);
         expect(commitFiles).toHaveBeenCalledWith(
           buildExpectedCommitFilesArgument(
@@ -84,7 +84,7 @@ describe('workers/repository/onboarding/branch', () => {
         const prefix = 'chore(deps)';
         const message =
           'I say, we can update when we want to, a commit they will never mind';
-        config.semanticCommits = true;
+        config.semanticCommits = 'enabled';
         config.onboardingCommitMessage = message;
         await createOnboardingBranch(config);
         expect(commitFiles).toHaveBeenCalledWith(
diff --git a/lib/workers/repository/onboarding/branch/create.ts b/lib/workers/repository/onboarding/branch/create.ts
index 1777fc57a1e4e251db3c9cd79281ef8176817e42..e1792c357be2e7a7b984e14667b912ce4a6cc35f 100644
--- a/lib/workers/repository/onboarding/branch/create.ts
+++ b/lib/workers/repository/onboarding/branch/create.ts
@@ -17,7 +17,7 @@ export function createOnboardingBranch(
   let commitMessagePrefix = '';
   if (config.commitMessagePrefix) {
     commitMessagePrefix = config.commitMessagePrefix;
-  } else if (config.semanticCommits) {
+  } else if (config.semanticCommits === 'enabled') {
     commitMessagePrefix = config.semanticCommitType;
     if (config.semanticCommitScope) {
       commitMessagePrefix += `(${config.semanticCommitScope})`;
diff --git a/lib/workers/repository/onboarding/branch/rebase.ts b/lib/workers/repository/onboarding/branch/rebase.ts
index 73096472eef18980244d24bf781c64313c1729f8..57fb9d034b9a8c6abe6ad9738c548d736c026b94 100644
--- a/lib/workers/repository/onboarding/branch/rebase.ts
+++ b/lib/workers/repository/onboarding/branch/rebase.ts
@@ -14,7 +14,7 @@ const defaultConfigFile = configFileNames[0];
 function getCommitMessage(config: RenovateConfig): string {
   let commitMessage: string;
   // istanbul ignore if
-  if (config.semanticCommits) {
+  if (config.semanticCommits === 'enabled') {
     commitMessage = config.semanticCommitType;
     if (config.semanticCommitScope) {
       commitMessage += `(${config.semanticCommitScope})`;
diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts
index 79e81a8cbc6b7b02b4f160e394687ab5fd2cf2a9..4317e68d2db3221cd39bb1739be19cf6a3534a47 100644
--- a/lib/workers/repository/updates/generate.spec.ts
+++ b/lib/workers/repository/updates/generate.spec.ts
@@ -219,7 +219,7 @@ describe('workers/repository/updates/generate', () => {
         partial<BranchUpgradeConfig>({
           ...defaultConfig,
           depName: 'some-dep',
-          semanticCommits: true,
+          semanticCommits: 'enabled',
           semanticCommitType: 'chore',
           semanticCommitScope: 'package',
           newValue: '1.2.0',
@@ -243,7 +243,7 @@ describe('workers/repository/updates/generate', () => {
           depName: 'some-dep',
           packageFile: 'package.json',
           baseDir: '',
-          semanticCommits: true,
+          semanticCommits: 'enabled',
           semanticCommitType: 'chore',
           semanticCommitScope: '{{baseDir}}',
           newValue: '1.2.0',
@@ -268,7 +268,7 @@ describe('workers/repository/updates/generate', () => {
           depName: 'some-dep',
           packageFile: 'foo/bar/package.json',
           parentDir: 'bar',
-          semanticCommits: true,
+          semanticCommits: 'enabled',
           semanticCommitType: 'chore',
           semanticCommitScope: '{{parentDir}}',
           newValue: '1.2.0',
@@ -292,7 +292,7 @@ describe('workers/repository/updates/generate', () => {
           depName: 'some-dep',
           packageFile: 'foo/bar/package.json',
           baseDir: 'foo/bar',
-          semanticCommits: true,
+          semanticCommits: 'enabled',
           semanticCommitType: 'chore',
           semanticCommitScope: '{{baseDir}}',
           newValue: '1.2.0',
diff --git a/lib/workers/repository/updates/generate.ts b/lib/workers/repository/updates/generate.ts
index e115b0a19469f18ffd53823356b932cb1b576b67..a53c3a4909c279b990324a4717a3bc2fa53f468f 100644
--- a/lib/workers/repository/updates/generate.ts
+++ b/lib/workers/repository/updates/generate.ts
@@ -173,7 +173,7 @@ export function generateBranchConfig(
       upgrade.isRange = false;
     }
     // Use templates to generate strings
-    if (upgrade.semanticCommits && !upgrade.commitMessagePrefix) {
+    if (upgrade.semanticCommits === 'enabled' && !upgrade.commitMessagePrefix) {
       logger.trace('Upgrade has semantic commits enabled');
       let semanticPrefix = upgrade.semanticCommitType;
       if (upgrade.semanticCommitScope) {