diff --git a/lib/modules/datasource/packagist/schema.ts b/lib/modules/datasource/packagist/schema.ts
index 31c0ce0100a3ea8f5cfc77e952aeaf68f8750de3..e2929c33e1984be4152ae14c42b5d87b4daa946d 100644
--- a/lib/modules/datasource/packagist/schema.ts
+++ b/lib/modules/datasource/packagist/schema.ts
@@ -1,7 +1,11 @@
 import is from '@sindresorhus/is';
 import { z } from 'zod';
 import { logger } from '../../../logger';
-import { looseArray, looseRecord, looseValue } from '../../../util/schema';
+import {
+  looseArray,
+  looseRecord,
+  looseValue,
+} from '../../../util/schema-utils';
 import type { Release, ReleaseResult } from '../types';
 
 export const MinifiedArray = z.array(z.record(z.unknown())).transform((xs) => {
diff --git a/lib/modules/platform/bitbucket-server/utils.ts b/lib/modules/platform/bitbucket-server/utils.ts
index ea10aad605dc75bc9be9e52ceedaa92d5f268a97..a54e573b54663e4c52ae14275d51864ef108f5e6 100644
--- a/lib/modules/platform/bitbucket-server/utils.ts
+++ b/lib/modules/platform/bitbucket-server/utils.ts
@@ -61,7 +61,9 @@ function callApi<T>(
     case 'patch':
       return bitbucketServerHttp.patchJson<T>(apiUrl, options);
     case 'head':
-      return bitbucketServerHttp.headJson<T>(apiUrl, options);
+      return bitbucketServerHttp.headJson(apiUrl, options) as Promise<
+        HttpResponse<T>
+      >;
     case 'delete':
       return bitbucketServerHttp.deleteJson<T>(apiUrl, options);
     case 'get':
diff --git a/lib/modules/platform/bitbucket/utils.ts b/lib/modules/platform/bitbucket/utils.ts
index 7abfffb14ca633917a1eabdd5a1fdd766eb976a2..5466351d437db57cf36e1055ebc7a229cec0a8e5 100644
--- a/lib/modules/platform/bitbucket/utils.ts
+++ b/lib/modules/platform/bitbucket/utils.ts
@@ -86,7 +86,9 @@ function callApi<T>(
     case 'patch':
       return bitbucketHttp.patchJson<T>(apiUrl, options);
     case 'head':
-      return bitbucketHttp.headJson<T>(apiUrl, options);
+      return bitbucketHttp.headJson(apiUrl, options) as Promise<
+        HttpResponse<T>
+      >;
     case 'delete':
       return bitbucketHttp.deleteJson<T>(apiUrl, options);
     case 'get':
diff --git a/lib/modules/platform/github/common.ts b/lib/modules/platform/github/common.ts
index db8a22b3c84dcbaed2d02d8f3c0f2287a2aa6743..8fac76f9ee052ac8e461b78f2348f39b73d6c41c 100644
--- a/lib/modules/platform/github/common.ts
+++ b/lib/modules/platform/github/common.ts
@@ -1,7 +1,5 @@
 import is from '@sindresorhus/is';
-import * as schema from '../../../util/schema';
 import { getPrBodyStruct } from '../pr-body';
-import * as platformSchemas from '../schemas';
 import type { GhPr, GhRestPr } from './types';
 
 /**
@@ -54,6 +52,5 @@ export function coerceRestPr(pr: GhRestPr): GhPr {
     result.targetBranch = pr.base.ref;
   }
 
-  schema.match(platformSchemas.Pr, result, 'warn');
   return result;
 }
diff --git a/lib/modules/platform/schemas.ts b/lib/modules/platform/schemas.ts
deleted file mode 100644
index ac905936bff63f6ba529cdfbc5483a2d79953572..0000000000000000000000000000000000000000
--- a/lib/modules/platform/schemas.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { z } from 'zod';
-
-export const Pr = z.object(
-  {
-    sourceBranch: z.string().min(1),
-    number: z.number(),
-    state: z.string().min(1),
-    title: z.string().min(1),
-  },
-  {
-    description: 'Pull Request',
-  }
-);
diff --git a/lib/util/cache/repository/impl/base.ts b/lib/util/cache/repository/impl/base.ts
index c7a7e78da5a68ac22330256d2bf025a01572a20a..0114419b17dd0f259be95a438c32ec22bc1218bb 100644
--- a/lib/util/cache/repository/impl/base.ts
+++ b/lib/util/cache/repository/impl/base.ts
@@ -3,10 +3,9 @@ import hasha from 'hasha';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import { compress, decompress } from '../../../compress';
-import * as schema from '../../../schema';
 import { safeStringify } from '../../../stringify';
 import { CACHE_REVISION } from '../common';
-import { RepoCacheRecord, RepoCacheV13 } from '../schemas';
+import { RepoCacheRecord, RepoCacheV13 } from '../schema';
 import type { RepoCache, RepoCacheData } from '../types';
 
 export abstract class RepoCacheBase implements RepoCache {
@@ -44,8 +43,9 @@ export abstract class RepoCacheBase implements RepoCache {
       }
       const oldCache = JSON.parse(rawOldCache) as unknown;
 
-      if (schema.match(RepoCacheV13, oldCache)) {
-        await this.restore(oldCache);
+      const cacheV13 = RepoCacheV13.safeParse(oldCache);
+      if (cacheV13.success) {
+        await this.restore(cacheV13.data);
         logger.debug('Repository cache is restored from revision 13');
         return;
       }
diff --git a/lib/util/cache/repository/impl/local.spec.ts b/lib/util/cache/repository/impl/local.spec.ts
index 80840ac124f9020fbe9ce7f4b1fff708d1558188..c6f8d50b07e426835b599812b0cf318b88f3a684 100644
--- a/lib/util/cache/repository/impl/local.spec.ts
+++ b/lib/util/cache/repository/impl/local.spec.ts
@@ -4,7 +4,7 @@ import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import { compress } from '../../../compress';
 import { CACHE_REVISION } from '../common';
-import type { RepoCacheRecord } from '../schemas';
+import type { RepoCacheRecord } from '../schema';
 import type { RepoCacheData } from '../types';
 import { CacheFactory } from './cache-factory';
 import { RepoCacheLocal } from './local';
diff --git a/lib/util/cache/repository/impl/local.ts b/lib/util/cache/repository/impl/local.ts
index ffed379566bf62765690feb383976bd9d115a130..c2d485b6803a29d6d69e9aa467ef8ac251432f79 100644
--- a/lib/util/cache/repository/impl/local.ts
+++ b/lib/util/cache/repository/impl/local.ts
@@ -2,7 +2,7 @@ import upath from 'upath';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import { cachePathExists, outputCacheFile, readCacheFile } from '../../../fs';
-import type { RepoCacheRecord } from '../schemas';
+import type { RepoCacheRecord } from '../schema';
 import { RepoCacheBase } from './base';
 
 export class RepoCacheLocal extends RepoCacheBase {
diff --git a/lib/util/cache/repository/impl/s3.spec.ts b/lib/util/cache/repository/impl/s3.spec.ts
index fa10708369b50352c5f14fa2c66d684420d05562..a981dbef710c87dfc6de91bc5ff018840f0cacc4 100644
--- a/lib/util/cache/repository/impl/s3.spec.ts
+++ b/lib/util/cache/repository/impl/s3.spec.ts
@@ -12,7 +12,7 @@ import { partial } from '../../../../../test/util';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
 import { parseS3Url } from '../../../s3';
-import type { RepoCacheRecord } from '../schemas';
+import type { RepoCacheRecord } from '../schema';
 import { CacheFactory } from './cache-factory';
 import { RepoCacheS3 } from './s3';
 
diff --git a/lib/util/cache/repository/impl/s3.ts b/lib/util/cache/repository/impl/s3.ts
index 87ff00ae82fdd730a9a84e58759bc18be68b2b79..f402409acab70740b7fa64e9d38e49a18889b260 100644
--- a/lib/util/cache/repository/impl/s3.ts
+++ b/lib/util/cache/repository/impl/s3.ts
@@ -8,7 +8,7 @@ import {
 import { logger } from '../../../../logger';
 import { getS3Client, parseS3Url } from '../../../s3';
 import { streamToString } from '../../../streams';
-import type { RepoCacheRecord } from '../schemas';
+import type { RepoCacheRecord } from '../schema';
 import { RepoCacheBase } from './base';
 
 export class RepoCacheS3 extends RepoCacheBase {
diff --git a/lib/util/cache/repository/schemas.ts b/lib/util/cache/repository/schema.ts
similarity index 100%
rename from lib/util/cache/repository/schemas.ts
rename to lib/util/cache/repository/schema.ts
diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts
index 97226e30c161fa698ebd50050c66907afe92c283..811d3dfd7e6e6e7efe6f0a505569697196e67bee 100644
--- a/lib/util/http/index.spec.ts
+++ b/lib/util/http/index.spec.ts
@@ -1,4 +1,4 @@
-import * as z from 'zod';
+import { z } from 'zod';
 import * as httpMock from '../../../test/http-mock';
 import { logger } from '../../../test/util';
 import {
@@ -7,7 +7,6 @@ import {
 } from '../../constants/error-messages';
 import * as memCache from '../cache/memory';
 import * as hostRules from '../host-rules';
-import { reportErrors } from '../schema';
 import * as queue from './queue';
 import * as throttle from './throttle';
 import type { HttpResponse } from './types';
@@ -316,8 +315,9 @@ describe('util/http/index', () => {
   });
 
   describe('Schema support', () => {
-    const testSchema = z.object({ test: z.boolean() });
-    type TestType = z.infer<typeof testSchema>;
+    const SomeSchema = z
+      .object({ x: z.number(), y: z.number() })
+      .transform(({ x, y }) => `${x} + ${y} = ${x + y}`);
 
     beforeEach(() => {
       jest.resetAllMocks();
@@ -329,7 +329,7 @@ describe('util/http/index', () => {
     });
 
     describe('getJson', () => {
-      it('infers body type', async () => {
+      it('uses schema for response body', async () => {
         httpMock
           .scope(baseUrl, {
             reqheaders: {
@@ -337,21 +337,19 @@ describe('util/http/index', () => {
             },
           })
           .get('/')
-          .reply(200, JSON.stringify({ test: true }));
+          .reply(200, JSON.stringify({ x: 2, y: 2 }));
 
-        const { body }: HttpResponse<TestType> = await http.getJson(
+        const { body }: HttpResponse<string> = await http.getJson(
           'http://renovate.com',
-          testSchema
+          { headers: { accept: 'application/json' } },
+          SomeSchema
         );
 
-        expect(body).toEqual({ test: true });
-
-        reportErrors();
-        expect(logger.logger.warn).not.toHaveBeenCalled();
+        expect(body).toBe('2 + 2 = 4');
+        expect(logger.logger.once.info).not.toHaveBeenCalled();
       });
 
-      it('reports warnings', async () => {
-        memCache.init();
+      it('returns original body if schema does not match', async () => {
         httpMock
           .scope(baseUrl, {
             reqheaders: {
@@ -359,98 +357,41 @@ describe('util/http/index', () => {
             },
           })
           .get('/')
-          .reply(200, JSON.stringify({ test: 'foobar' }));
-
-        const res = await http.getJson(
-          'http://renovate.com',
-          { onSchemaError: 'warn' },
-          testSchema
-        );
-
-        expect(res.body).toEqual({ test: 'foobar' });
+          .reply(200, JSON.stringify({ foo: 'bar' }));
 
-        expect(logger.logger.warn).not.toHaveBeenCalled();
-        reportErrors();
-        expect(logger.logger.warn).toHaveBeenCalled();
-      });
+        const { body } = await http.getJson('http://renovate.com', SomeSchema);
 
-      it('throws', async () => {
-        httpMock
-          .scope(baseUrl, {
-            reqheaders: {
-              accept: 'application/json',
-            },
-          })
-          .get('/')
-          .reply(200, JSON.stringify({ test: 'foobar' }));
-
-        await expect(
-          http.getJson(
-            'http://renovate.com',
-            { onSchemaError: 'throw' },
-            testSchema
-          )
-        ).rejects.toThrow();
-
-        reportErrors();
-        expect(logger.logger.warn).not.toHaveBeenCalled();
+        expect(body).toEqual({ foo: 'bar' });
+        expect(logger.logger.once.info).toHaveBeenCalled();
       });
     });
 
     describe('postJson', () => {
-      it('infers body type', async () => {
+      it('uses schema for response body', async () => {
         httpMock
           .scope(baseUrl)
           .post('/')
-          .reply(200, JSON.stringify({ test: true }));
+          .reply(200, JSON.stringify({ x: 2, y: 2 }));
 
-        const { body }: HttpResponse<TestType> = await http.postJson(
+        const { body }: HttpResponse<string> = await http.postJson(
           'http://renovate.com',
-          testSchema
+          SomeSchema
         );
 
-        expect(body).toEqual({ test: true });
-
-        reportErrors();
-        expect(logger.logger.warn).not.toHaveBeenCalled();
+        expect(body).toBe('2 + 2 = 4');
+        expect(logger.logger.once.info).not.toHaveBeenCalled();
       });
 
-      it('reports warnings', async () => {
-        memCache.init();
+      it('returns original body if schema does not match', async () => {
         httpMock
           .scope(baseUrl)
           .post('/')
-          .reply(200, JSON.stringify({ test: 'foobar' }));
-
-        const res = await http.postJson(
-          'http://renovate.com',
-          { onSchemaError: 'warn' },
-          testSchema
-        );
+          .reply(200, JSON.stringify({ foo: 'bar' }));
 
-        expect(res.body).toEqual({ test: 'foobar' });
+        const { body } = await http.postJson('http://renovate.com', SomeSchema);
 
-        expect(logger.logger.warn).not.toHaveBeenCalled();
-        reportErrors();
-        expect(logger.logger.warn).toHaveBeenCalled();
-      });
-
-      it('throws', async () => {
-        httpMock
-          .scope(baseUrl)
-          .post('/')
-          .reply(200, JSON.stringify({ test: 'foobar' }));
-
-        await expect(
-          http.postJson(
-            'http://renovate.com',
-            { onSchemaError: 'throw' },
-            testSchema
-          )
-        ).rejects.toThrow();
-
-        reportErrors();
-        expect(logger.logger.warn).not.toHaveBeenCalled();
+        expect(body).toEqual({ foo: 'bar' });
+        expect(logger.logger.once.info).toHaveBeenCalled();
       });
     });
   });
diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts
index 43dc5237056945c66a3ec6a83c00759d9b9d50bd..31c3d886e43422476cf5c316fad6c65c81621a57 100644
--- a/lib/util/http/index.ts
+++ b/lib/util/http/index.ts
@@ -1,14 +1,13 @@
 import merge from 'deepmerge';
 import got, { Options, RequestError } from 'got';
 import hasha from 'hasha';
-import { infer as Infer, ZodSchema } from 'zod';
+import { infer as Infer, ZodType } from 'zod';
 import { HOST_DISABLED } from '../../constants/error-messages';
 import { pkg } from '../../expose.cjs';
 import { logger } from '../../logger';
 import { ExternalHostError } from '../../types/errors/external-host-error';
 import * as memCache from '../cache/memory';
 import { clone } from '../clone';
-import { match } from '../schema';
 import { resolveBaseUrl } from '../url';
 import { applyAuthorization, removeAuthorization } from './auth';
 import { hooks } from './hooks';
@@ -28,10 +27,14 @@ import './legacy';
 
 export { RequestError as HttpError };
 
-type JsonArgs<T extends HttpOptions> = {
+type JsonArgs<
+  Opts extends HttpOptions,
+  ResT = unknown,
+  Schema extends ZodType<ResT> = ZodType<ResT>
+> = {
   url: string;
-  httpOptions?: T;
-  schema?: ZodSchema | undefined;
+  httpOptions?: Opts;
+  schema?: Schema;
 };
 
 type Task<T> = () => Promise<HttpResponse<T>>;
@@ -235,11 +238,11 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
     return this.requestBuffer(url, options);
   }
 
-  private async requestJson<T = unknown>(
+  private async requestJson<ResT = unknown>(
     method: InternalHttpOptions['method'],
-    { url, httpOptions: requestOptions, schema }: JsonArgs<Opts>
-  ): Promise<HttpResponse<T>> {
-    const { body, onSchemaError, ...httpOptions } = { ...requestOptions };
+    { url, httpOptions: requestOptions, schema }: JsonArgs<Opts, ResT>
+  ): Promise<HttpResponse<ResT>> {
+    const { body, ...httpOptions } = { ...requestOptions };
     const opts: InternalHttpOptions = {
       ...httpOptions,
       method,
@@ -253,23 +256,32 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
     if (body) {
       opts.json = body;
     }
-    const res = await this.request<T>(url, opts);
+    const res = await this.request<ResT>(url, opts);
+
+    if (!schema) {
+      return { ...res, body: res.body };
+    }
 
-    if (schema) {
-      match(schema, res.body, onSchemaError);
+    const parsed = await schema.safeParseAsync(res.body);
+    if (!parsed.success) {
+      logger.once.info(
+        { err: parsed.error },
+        `Response does not match schema: please report this to https://github.com/renovatebot/renovate/pull/21338`
+      );
+      return { ...res, body: res.body };
     }
 
-    return { ...res, body: res.body };
+    return { ...res, body: parsed.data };
   }
 
-  private resolveArgs(
+  private resolveArgs<ResT = unknown>(
     arg1: string,
-    arg2: Opts | ZodSchema | undefined,
-    arg3: ZodSchema | undefined
-  ): JsonArgs<Opts> {
-    const res: JsonArgs<Opts> = { url: arg1 };
+    arg2: Opts | ZodType<ResT> | undefined,
+    arg3: ZodType<ResT> | undefined
+  ): JsonArgs<Opts, ResT> {
+    const res: JsonArgs<Opts, ResT> = { url: arg1 };
 
-    if (arg2 instanceof ZodSchema) {
+    if (arg2 instanceof ZodType<ResT>) {
       res.schema = arg2;
     } else if (arg2) {
       res.httpOptions = arg2;
@@ -282,115 +294,100 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
     return res;
   }
 
-  getJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  getJson<T>(
+  getJson<ResT>(url: string, options?: Opts): Promise<HttpResponse<ResT>>;
+  getJson<ResT, Schema extends ZodType<ResT> = ZodType<ResT>>(
     url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  getJson<T>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  getJson<ResT, Schema extends ZodType<ResT> = ZodType<ResT>>(
     url: string,
     options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  getJson<T = unknown>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  getJson<ResT = unknown, Schema extends ZodType<ResT> = ZodType<ResT>>(
     arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
-  ): Promise<HttpResponse<T>> {
-    const args = this.resolveArgs(arg1, arg2, arg3);
-    return this.requestJson<T>('get', args);
+    arg2?: Opts | Schema,
+    arg3?: Schema
+  ): Promise<HttpResponse<ResT>> {
+    const args = this.resolveArgs<ResT>(arg1, arg2, arg3);
+    return this.requestJson<ResT>('get', args);
   }
 
-  headJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  headJson<T>(
-    url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  headJson<T>(
-    url: string,
-    options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  headJson<T = unknown>(
-    arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
-  ): Promise<HttpResponse<T>> {
-    const args = this.resolveArgs(arg1, arg2, arg3);
-    return this.requestJson<T>('head', args);
+  headJson(url: string, httpOptions?: Opts): Promise<HttpResponse<never>> {
+    return this.requestJson<never>('head', { url, httpOptions });
   }
 
   postJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  postJson<T>(
+  postJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  postJson<T>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  postJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
     options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  postJson<T = unknown>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  postJson<T = unknown, Schema extends ZodType<T> = ZodType<T>>(
     arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
+    arg2?: Opts | Schema,
+    arg3?: Schema
   ): Promise<HttpResponse<T>> {
     const args = this.resolveArgs(arg1, arg2, arg3);
     return this.requestJson<T>('post', args);
   }
 
   putJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  putJson<T>(
+  putJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  putJson<T>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  putJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
     options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  putJson<T = unknown>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  putJson<T = unknown, Schema extends ZodType<T> = ZodType<T>>(
     arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
+    arg2?: Opts | Schema,
+    arg3?: ZodType
   ): Promise<HttpResponse<T>> {
     const args = this.resolveArgs(arg1, arg2, arg3);
     return this.requestJson<T>('put', args);
   }
 
   patchJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  patchJson<T>(
+  patchJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  patchJson<T>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  patchJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
     options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  patchJson<T = unknown>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  patchJson<T = unknown, Schema extends ZodType<T> = ZodType<T>>(
     arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
+    arg2?: Opts | Schema,
+    arg3?: Schema
   ): Promise<HttpResponse<T>> {
     const args = this.resolveArgs(arg1, arg2, arg3);
     return this.requestJson<T>('patch', args);
   }
 
   deleteJson<T>(url: string, options?: Opts): Promise<HttpResponse<T>>;
-  deleteJson<T>(
+  deleteJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  deleteJson<T>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  deleteJson<T, Schema extends ZodType<T> = ZodType<T>>(
     url: string,
     options: Opts,
-    schema: ZodSchema<T>
-  ): Promise<HttpResponse<Infer<typeof schema>>>;
-  deleteJson<T = unknown>(
+    schema: Schema
+  ): Promise<HttpResponse<Infer<Schema>>>;
+  deleteJson<T = unknown, Schema extends ZodType<T> = ZodType<T>>(
     arg1: string,
-    arg2?: Opts | ZodSchema,
-    arg3?: ZodSchema
+    arg2?: Opts | Schema,
+    arg3?: Schema
   ): Promise<HttpResponse<T>> {
     const args = this.resolveArgs(arg1, arg2, arg3);
     return this.requestJson<T>('delete', args);
diff --git a/lib/util/http/types.ts b/lib/util/http/types.ts
index 8616e9c121a0013cb1a97c04e34d4bf3ab8bfa68..4cf1774683eb422b73ad2b64482522910c5555b7 100644
--- a/lib/util/http/types.ts
+++ b/lib/util/http/types.ts
@@ -63,8 +63,6 @@ export interface HttpOptions {
 
   token?: string;
   useCache?: boolean;
-
-  onSchemaError?: 'warn' | 'throw';
 }
 
 export interface InternalHttpOptions extends HttpOptions {
diff --git a/lib/util/schema.spec.ts b/lib/util/schema-utils.spec.ts
similarity index 52%
rename from lib/util/schema.spec.ts
rename to lib/util/schema-utils.spec.ts
index 3bcabb285c196a9c9bebea49f5c2a798d821a5a5..10af9db538eb20c4e0a2c1f546c2052b41068b82 100644
--- a/lib/util/schema.spec.ts
+++ b/lib/util/schema-utils.spec.ts
@@ -1,87 +1,7 @@
 import { z } from 'zod';
-import { logger } from '../../test/util';
-import * as memCache from './cache/memory';
-import * as schema from './schema';
-
-describe('util/schema', () => {
-  beforeEach(() => {
-    jest.resetAllMocks();
-    memCache.init();
-  });
-
-  it('validates data', () => {
-    const testSchema = z.object({ foo: z.string() });
-    const validData = { foo: 'bar' };
-
-    const res = schema.match(testSchema, validData);
-    expect(res).toBeTrue();
-  });
-
-  it('returns false for invalid data', () => {
-    const testSchema = z.object({ foo: z.string() });
-    const invalidData = { foo: 123 };
-
-    const res = schema.match(testSchema, invalidData);
-    expect(res).toBeFalse();
-
-    schema.reportErrors();
-    expect(logger.logger.warn).not.toHaveBeenCalled();
-  });
-
-  describe('warn', () => {
-    it('reports nothing if there are no any reports', () => {
-      schema.reportErrors();
-      expect(logger.logger.warn).not.toHaveBeenCalled();
-    });
-
-    it('reports same warning one time', () => {
-      const testSchema = z.object(
-        { foo: z.string() },
-        { description: 'Some test schema' }
-      );
-      const invalidData = { foo: 42 };
-
-      schema.match(testSchema, invalidData, 'warn');
-      schema.match(testSchema, invalidData, 'warn');
-      schema.match(testSchema, invalidData, 'warn');
-      schema.match(testSchema, invalidData, 'warn');
-      schema.reportErrors();
-
-      expect(logger.logger.warn).toHaveBeenCalledOnce();
-      expect(logger.logger.warn.mock.calls[0]).toMatchObject([
-        { description: 'Some test schema' },
-        'Schema validation error',
-      ]);
-    });
-
-    it('reports unspecified schema', () => {
-      const testSchema = z.object({ foo: z.string() });
-      const invalidData = { foo: 42 };
-
-      schema.match(testSchema, invalidData, 'warn');
-      schema.reportErrors();
-
-      expect(logger.logger.warn).toHaveBeenCalledOnce();
-      expect(logger.logger.warn.mock.calls[0]).toMatchObject([
-        { description: 'Unspecified schema' },
-        'Schema validation error',
-      ]);
-    });
-  });
-
-  describe('throw', () => {
-    it('throws for invalid data', () => {
-      const testSchema = z.object({
-        foo: z.string({ invalid_type_error: 'foobar' }),
-      });
-      const invalidData = { foo: 123 };
-
-      expect(() => schema.match(testSchema, invalidData, 'throw')).toThrow(
-        'foobar'
-      );
-    });
-  });
+import * as schema from './schema-utils';
 
+describe('util/schema-utils', () => {
   describe('looseArray', () => {
     it('parses array', () => {
       const s = schema.looseArray(z.string());
diff --git a/lib/util/schema.ts b/lib/util/schema-utils.ts
similarity index 52%
rename from lib/util/schema.ts
rename to lib/util/schema-utils.ts
index 3d6f0254a8cc709fceeb4ef8f9d8fbc9f68ee812..781cca9d8ea98162870c1c44c4438ab40e44b596 100644
--- a/lib/util/schema.ts
+++ b/lib/util/schema-utils.ts
@@ -1,71 +1,4 @@
-import is from '@sindresorhus/is';
-import hasha from 'hasha';
 import { z } from 'zod';
-import { logger } from '../logger';
-import * as memCache from './cache/memory';
-import { safeStringify } from './stringify';
-
-type SchemaErrorsMap = Record<string, Record<string, z.ZodError>>;
-
-function getCacheKey(error: z.ZodError): string {
-  const content = safeStringify(error);
-  const key = hasha(content).slice(0, 32);
-  return `err_${key}`;
-}
-
-function collectError<T extends z.ZodSchema>(
-  schema: T,
-  error: z.ZodError
-): void {
-  const { description = 'Unspecified schema' } = schema;
-  const schemaErrorsMap = memCache.get<SchemaErrorsMap>('schema-errors') ?? {};
-  const schemaErrors = schemaErrorsMap[description] ?? {};
-  const key = getCacheKey(error);
-  const schemaError = schemaErrors[key];
-  if (!schemaError) {
-    schemaErrors[key] = error;
-    schemaErrorsMap[description] = schemaErrors;
-  }
-  memCache.set('schema-errors', schemaErrorsMap);
-}
-
-export function reportErrors(): void {
-  const schemaErrorsMap = memCache.get<SchemaErrorsMap>('schema-errors');
-  if (!schemaErrorsMap) {
-    return;
-  }
-
-  for (const [description, schemaErrors] of Object.entries(schemaErrorsMap)) {
-    const errors = Object.values(schemaErrors);
-    for (const err of errors) {
-      logger.warn({ description, err }, `Schema validation error`);
-    }
-  }
-
-  memCache.set('schema-errors', null);
-}
-
-export function match<T extends z.ZodSchema>(
-  schema: T,
-  input: unknown,
-  onError?: 'warn' | 'throw'
-): input is z.infer<T> {
-  const res = schema.safeParse(input);
-  const { success } = res;
-  if (!success) {
-    if (onError === 'warn') {
-      collectError(schema, res.error);
-    }
-
-    if (onError === 'throw') {
-      throw res.error;
-    }
-
-    return false;
-  }
-
-  return true;
-}
 
 export function looseArray<T extends z.ZodTypeAny>(
   schema: T,
@@ -96,7 +29,7 @@ export function looseArray<T extends z.ZodTypeAny>(
     : arrayOfNullables.catch([]);
 
   const filteredArray = arrayWithFallback.transform((xs) =>
-    xs.filter((x): x is Elem => !is.null_(x))
+    xs.filter((x): x is Elem => x !== null)
   );
 
   return filteredArray;
@@ -133,7 +66,7 @@ export function looseRecord<T extends z.ZodTypeAny>(
   const filteredRecord = recordWithFallback.transform(
     (rec): Record<string, Elem> => {
       for (const key of Object.keys(rec)) {
-        if (is.null_(rec[key])) {
+        if (rec[key] === null) {
           delete rec[key];
         }
       }
diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts
index d5a5474d41a6f5a5aaf9d533eb5dc3fcd90a088a..45103753917f34b5617adbd59ff9a14883c7e421 100644
--- a/lib/workers/repository/index.ts
+++ b/lib/workers/repository/index.ts
@@ -17,7 +17,6 @@ import { detectSemanticCommits } from '../../util/git/semantic';
 import { clearDnsCache, printDnsStats } from '../../util/http/dns';
 import * as queue from '../../util/http/queue';
 import * as throttle from '../../util/http/throttle';
-import * as schemaUtil from '../../util/schema';
 import { addSplit, getSplits, splitInit } from '../../util/split';
 import { setBranchCache } from './cache';
 import { ensureDependencyDashboard } from './dependency-dashboard';
@@ -126,7 +125,6 @@ export async function renovateRepository(
   printLookupStats();
   printDnsStats();
   clearDnsCache();
-  schemaUtil.reportErrors();
   const cloned = isCloned();
   logger.info({ cloned, durationMs: splits.total }, 'Repository finished');
   return repoResult;