diff --git a/lib/modules/platform/github/common.ts b/lib/modules/platform/github/common.ts
index 2b9e146bab655244bba016fc5b42af2e2284b267..9a4853d3c2790527f0d6fe1b3b8058770f83b2fd 100644
--- a/lib/modules/platform/github/common.ts
+++ b/lib/modules/platform/github/common.ts
@@ -1,6 +1,8 @@
 import is from '@sindresorhus/is';
 import { PrState } from '../../../types';
+import { checkSchema } from '../../../util/schema';
 import { getPrBodyStruct } from '../pr-body';
+import * as platformSchemas from '../schemas';
 import type { GhPr, GhRestPr } from './types';
 
 /**
@@ -50,5 +52,6 @@ export function coerceRestPr(pr: GhRestPr): GhPr {
     result.closedAt = pr.closed_at;
   }
 
+  checkSchema(platformSchemas.Pr, result);
   return result;
 }
diff --git a/lib/modules/platform/schemas.ts b/lib/modules/platform/schemas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ac905936bff63f6ba529cdfbc5483a2d79953572
--- /dev/null
+++ b/lib/modules/platform/schemas.ts
@@ -0,0 +1,13 @@
+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/schema.spec.ts b/lib/util/schema.spec.ts
new file mode 100644
index 0000000000000000000000000000000000000000..608b8dadc239460e8061359c65beca4177784298
--- /dev/null
+++ b/lib/util/schema.spec.ts
@@ -0,0 +1,61 @@
+import { z } from 'zod';
+import { logger } from '../../test/util';
+import * as memCache from './cache/memory';
+import { checkSchema, reportErrors } from './schema';
+
+describe('util/schema', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+    memCache.init();
+  });
+
+  it('validates data', () => {
+    const schema = z.object({ foo: z.string() });
+    const validData = { foo: 'bar' };
+
+    const res = checkSchema(schema, validData);
+    expect(res).toBeTrue();
+
+    reportErrors();
+    expect(logger.logger.warn).not.toHaveBeenCalledOnce();
+  });
+
+  it('reports nothing if there are no any reports', () => {
+    reportErrors();
+    expect(logger.logger.warn).not.toHaveBeenCalled();
+  });
+
+  it('reports same warning once', () => {
+    const schema = z.object(
+      { foo: z.string() },
+      { description: 'Some test schema' }
+    );
+    const invalidData = { foo: 42 };
+
+    checkSchema(schema, invalidData);
+    checkSchema(schema, invalidData);
+    checkSchema(schema, invalidData);
+    checkSchema(schema, invalidData);
+    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 schema = z.object({ foo: z.string() });
+    const invalidData = { foo: 42 };
+
+    checkSchema(schema, invalidData);
+    reportErrors();
+
+    expect(logger.logger.warn).toHaveBeenCalledOnce();
+    expect(logger.logger.warn.mock.calls[0]).toMatchObject([
+      { description: 'Unspecified schema' },
+      'Schema validation error',
+    ]);
+  });
+});
diff --git a/lib/util/schema.ts b/lib/util/schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22e91073793c5ec88b542e50067b62aa62fdb00d
--- /dev/null
+++ b/lib/util/schema.ts
@@ -0,0 +1,59 @@
+import hasha from 'hasha';
+import type { 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 checkSchema<T extends z.ZodSchema>(
+  schema: T,
+  input: unknown
+): input is z.infer<T> {
+  const res = schema.safeParse(input);
+  const { success } = res;
+  if (!success) {
+    collectError(schema, res.error);
+    return false;
+  }
+
+  return true;
+}
diff --git a/lib/workers/repository/index.ts b/lib/workers/repository/index.ts
index 1ff989bc6d70569e5a4b03f1fe85f3ce9c8d42b5..c829742543b359b59eecf5de29483be702ad78f8 100644
--- a/lib/workers/repository/index.ts
+++ b/lib/workers/repository/index.ts
@@ -8,6 +8,7 @@ import { removeDanglingContainers } from '../../util/exec/docker';
 import { deleteLocalFile, privateCacheDir } from '../../util/fs';
 import { clearDnsCache, printDnsStats } from '../../util/http/dns';
 import * as queue from '../../util/http/queue';
+import * as schemaUtil from '../../util/schema';
 import { addSplit, getSplits, splitInit } from '../../util/split';
 import { setBranchCache } from './cache';
 import { ensureDependencyDashboard } from './dependency-dashboard';
@@ -89,6 +90,7 @@ export async function renovateRepository(
   printRequestStats();
   printDnsStats();
   clearDnsCache();
+  schemaUtil.reportErrors();
   logger.info({ durationMs: splits.total }, 'Repository finished');
   return repoResult;
 }
diff --git a/package.json b/package.json
index 70eb6ca59a245913898badfd7d8e6487ca8f1d1b..bb0fcbb6ee877b28a1e7d8f87419b6c26ab44828 100644
--- a/package.json
+++ b/package.json
@@ -219,7 +219,8 @@
     "upath": "2.0.1",
     "url-join": "4.0.1",
     "validate-npm-package-name": "4.0.0",
-    "xmldoc": "1.2.0"
+    "xmldoc": "1.2.0",
+    "zod": "3.19.1"
   },
   "optionalDependencies": {
     "re2": "1.17.7"
diff --git a/yarn.lock b/yarn.lock
index 13dcc55e7b332e15897612b23e57e2d13dc1e7ac..0d15c2c81e0b30458e756309ff64d4440bb85276 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9694,6 +9694,11 @@ yocto-queue@^0.1.0:
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
   integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
 
+zod@3.19.1:
+  version "3.19.1"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.1.tgz#112f074a97b50bfc4772d4ad1576814bd8ac4473"
+  integrity sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==
+
 zwitch@^1.0.0:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"