From d182874b705b8c993b34523724b100710ef3f2f9 Mon Sep 17 00:00:00 2001 From: er0k Date: Fri, 4 Sep 2026 20:00:57 -0400 Subject: [PATCH 1/4] feat: add option to exclude private GitHub repos from indexing fixes https://github.com/sourcebot-dev/sourcebot/issues/1631 --- .../snippets/schemas/v3/connection.schema.mdx | 5 ++++ docs/snippets/schemas/v3/github.schema.mdx | 5 ++++ docs/snippets/schemas/v3/index.schema.mdx | 5 ++++ packages/backend/src/github.test.ts | 24 +++++++++++++++++++ packages/backend/src/github.ts | 6 +++++ packages/schemas/src/v3/connection.schema.ts | 5 ++++ packages/schemas/src/v3/connection.type.ts | 4 ++++ packages/schemas/src/v3/github.schema.ts | 5 ++++ packages/schemas/src/v3/github.type.ts | 4 ++++ packages/schemas/src/v3/index.schema.ts | 5 ++++ packages/schemas/src/v3/index.type.ts | 4 ++++ schemas/v3/github.json | 5 ++++ 12 files changed, 77 insertions(+) diff --git a/docs/snippets/schemas/v3/connection.schema.mdx b/docs/snippets/schemas/v3/connection.schema.mdx index 2d4607b9d..940d0074b 100644 --- a/docs/snippets/schemas/v3/connection.schema.mdx +++ b/docs/snippets/schemas/v3/connection.schema.mdx @@ -125,6 +125,11 @@ "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/docs/snippets/schemas/v3/github.schema.mdx b/docs/snippets/schemas/v3/github.schema.mdx index 7d731cdc5..d1891b426 100644 --- a/docs/snippets/schemas/v3/github.schema.mdx +++ b/docs/snippets/schemas/v3/github.schema.mdx @@ -121,6 +121,11 @@ "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 60f08e149..0e9437da7 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -622,6 +622,11 @@ "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/packages/backend/src/github.test.ts b/packages/backend/src/github.test.ts index ce6c64077..ab5c31176 100644 --- a/packages/backend/src/github.test.ts +++ b/packages/backend/src/github.test.ts @@ -214,6 +214,30 @@ test('shouldExcludeRepo handles archived repos correctly', () => { expect(shouldExcludeRepo({ repo, exclude: { archived: false } })).toBe(false); }); +test('shouldExcludeRepo handles private repos correctly', () => { + const privateRepo = { + full_name: 'test/private-repo', + clone_url: 'https://github.com/test/private-repo.git', + private: true, + visibility: 'private', + } as OctokitRepository; + + expect(shouldExcludeRepo({ repo: privateRepo })).toBe(false); + expect(shouldExcludeRepo({ repo: privateRepo, exclude: { private: true } })).toBe(true); + expect(shouldExcludeRepo({ repo: privateRepo, exclude: { private: false } })).toBe(false); +}); + +test('shouldExcludeRepo does not exclude internal repos when exclude.private is true', () => { + const internalRepo = { + full_name: 'test/internal-repo', + clone_url: 'https://github.com/test/internal-repo.git', + private: true, + visibility: 'internal', + } as OctokitRepository; + + expect(shouldExcludeRepo({ repo: internalRepo, exclude: { private: true } })).toBe(false); +}); + test('shouldExcludeRepo handles include.topics correctly', () => { const repo = { full_name: 'test/repo', diff --git a/packages/backend/src/github.ts b/packages/backend/src/github.ts index 969de5438..173d221a3 100644 --- a/packages/backend/src/github.ts +++ b/packages/backend/src/github.ts @@ -77,6 +77,7 @@ export type OctokitRepository = { full_name: string, fork: boolean, private: boolean, + visibility?: string, html_url: string, clone_url?: string, stargazers_count?: number, @@ -507,6 +508,11 @@ export const shouldExcludeRepo = ({ return true; } + if (!!exclude?.private && repo.visibility === 'private') { + reason = `\`exclude.private\` is true`; + return true; + } + if (exclude?.repos) { if (micromatch.isMatch(repoName, exclude.repos)) { reason = `\`exclude.repos\` contains ${repoName}`; diff --git a/packages/schemas/src/v3/connection.schema.ts b/packages/schemas/src/v3/connection.schema.ts index 15d80600d..705b12bcb 100644 --- a/packages/schemas/src/v3/connection.schema.ts +++ b/packages/schemas/src/v3/connection.schema.ts @@ -124,6 +124,11 @@ const schema = { "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/packages/schemas/src/v3/connection.type.ts b/packages/schemas/src/v3/connection.type.ts index 7d85e86e7..6e47be6cb 100644 --- a/packages/schemas/src/v3/connection.type.ts +++ b/packages/schemas/src/v3/connection.type.ts @@ -61,6 +61,10 @@ export interface GithubConnectionConfig { * Exclude archived repositories from syncing. */ archived?: boolean; + /** + * Exclude private repositories from syncing. Internal repositories are not affected. + */ + private?: boolean; /** * List of individual repositories to exclude from syncing. Glob patterns are supported. */ diff --git a/packages/schemas/src/v3/github.schema.ts b/packages/schemas/src/v3/github.schema.ts index 93c61ba90..7f63d7ccf 100644 --- a/packages/schemas/src/v3/github.schema.ts +++ b/packages/schemas/src/v3/github.schema.ts @@ -120,6 +120,11 @@ const schema = { "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/packages/schemas/src/v3/github.type.ts b/packages/schemas/src/v3/github.type.ts index f7cdf4a2d..73195f67e 100644 --- a/packages/schemas/src/v3/github.type.ts +++ b/packages/schemas/src/v3/github.type.ts @@ -52,6 +52,10 @@ export interface GithubConnectionConfig { * Exclude archived repositories from syncing. */ archived?: boolean; + /** + * Exclude private repositories from syncing. Internal repositories are not affected. + */ + private?: boolean; /** * List of individual repositories to exclude from syncing. Glob patterns are supported. */ diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index 0d7a76d92..c39337979 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -621,6 +621,11 @@ const schema = { "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index 13027d9ff..f152649e2 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -292,6 +292,10 @@ export interface GithubConnectionConfig { * Exclude archived repositories from syncing. */ archived?: boolean; + /** + * Exclude private repositories from syncing. Internal repositories are not affected. + */ + private?: boolean; /** * List of individual repositories to exclude from syncing. Glob patterns are supported. */ diff --git a/schemas/v3/github.json b/schemas/v3/github.json index e431215a1..782498d9a 100644 --- a/schemas/v3/github.json +++ b/schemas/v3/github.json @@ -92,6 +92,11 @@ "default": false, "description": "Exclude archived repositories from syncing." }, + "private": { + "type": "boolean", + "default": false, + "description": "Exclude private repositories from syncing. Internal repositories are not affected." + }, "repos": { "type": "array", "items": { From e186c663f33936dd5e795f575be9eec413e1e5a6 Mon Sep 17 00:00:00 2001 From: er0k Date: Sat, 5 Sep 2026 01:15:41 +0000 Subject: [PATCH 2/4] Use repo.private with internal exemption for exclude.private Address cubic review: visibility can be null on GitHub AE and older GHES. Use repo.private as the primary filter and exempt internal repos via repo.visibility !== 'internal'. Co-Authored-By: Claude Opus 4.6 --- packages/backend/src/github.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/github.ts b/packages/backend/src/github.ts index 173d221a3..7593dbc39 100644 --- a/packages/backend/src/github.ts +++ b/packages/backend/src/github.ts @@ -508,7 +508,7 @@ export const shouldExcludeRepo = ({ return true; } - if (!!exclude?.private && repo.visibility === 'private') { + if (!!exclude?.private && repo.private && repo.visibility !== 'internal') { reason = `\`exclude.private\` is true`; return true; } From 5d16354fef53e92b0bfc790bdf134ca6be9fc2dc Mon Sep 17 00:00:00 2001 From: er0k Date: Sat, 5 Sep 2026 01:15:46 +0000 Subject: [PATCH 3/4] Add regression test for missing visibility field Address coderabbit review: cover the case where a private repo has no visibility field. The repo must still be excluded when exclude.private is true. Co-Authored-By: Claude Opus 4.6 --- packages/backend/src/github.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/backend/src/github.test.ts b/packages/backend/src/github.test.ts index ab5c31176..d6b6afd7b 100644 --- a/packages/backend/src/github.test.ts +++ b/packages/backend/src/github.test.ts @@ -238,6 +238,16 @@ test('shouldExcludeRepo does not exclude internal repos when exclude.private is expect(shouldExcludeRepo({ repo: internalRepo, exclude: { private: true } })).toBe(false); }); +test('shouldExcludeRepo excludes private repos with missing visibility when exclude.private is true', () => { + const repo = { + full_name: 'test/private-no-visibility', + clone_url: 'https://github.com/test/private-no-visibility.git', + private: true, + } as OctokitRepository; + + expect(shouldExcludeRepo({ repo, exclude: { private: true } })).toBe(true); +}); + test('shouldExcludeRepo handles include.topics correctly', () => { const repo = { full_name: 'test/repo', From 924481ef7052b448b321f5a2b8d9ba1b339dd36d Mon Sep 17 00:00:00 2001 From: er0k Date: Sat, 5 Sep 2026 01:15:53 +0000 Subject: [PATCH 4/4] Use second-person wording in exclude.private description Address coderabbit review: change description to "You can exclude private repositories from syncing." Regenerate all derived schema and documentation files. Co-Authored-By: Claude Opus 4.6 --- docs/snippets/schemas/v3/connection.schema.mdx | 2 +- docs/snippets/schemas/v3/github.schema.mdx | 2 +- docs/snippets/schemas/v3/index.schema.mdx | 2 +- packages/schemas/src/v3/connection.schema.ts | 2 +- packages/schemas/src/v3/connection.type.ts | 2 +- packages/schemas/src/v3/github.schema.ts | 2 +- packages/schemas/src/v3/github.type.ts | 2 +- packages/schemas/src/v3/index.schema.ts | 2 +- packages/schemas/src/v3/index.type.ts | 2 +- schemas/v3/github.json | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/snippets/schemas/v3/connection.schema.mdx b/docs/snippets/schemas/v3/connection.schema.mdx index 940d0074b..caff52d00 100644 --- a/docs/snippets/schemas/v3/connection.schema.mdx +++ b/docs/snippets/schemas/v3/connection.schema.mdx @@ -128,7 +128,7 @@ "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/docs/snippets/schemas/v3/github.schema.mdx b/docs/snippets/schemas/v3/github.schema.mdx index d1891b426..b6078b0fb 100644 --- a/docs/snippets/schemas/v3/github.schema.mdx +++ b/docs/snippets/schemas/v3/github.schema.mdx @@ -124,7 +124,7 @@ "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/docs/snippets/schemas/v3/index.schema.mdx b/docs/snippets/schemas/v3/index.schema.mdx index 0e9437da7..ba47e6ef8 100644 --- a/docs/snippets/schemas/v3/index.schema.mdx +++ b/docs/snippets/schemas/v3/index.schema.mdx @@ -625,7 +625,7 @@ "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/packages/schemas/src/v3/connection.schema.ts b/packages/schemas/src/v3/connection.schema.ts index 705b12bcb..c51ad0fda 100644 --- a/packages/schemas/src/v3/connection.schema.ts +++ b/packages/schemas/src/v3/connection.schema.ts @@ -127,7 +127,7 @@ const schema = { "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/packages/schemas/src/v3/connection.type.ts b/packages/schemas/src/v3/connection.type.ts index 6e47be6cb..a4b574207 100644 --- a/packages/schemas/src/v3/connection.type.ts +++ b/packages/schemas/src/v3/connection.type.ts @@ -62,7 +62,7 @@ export interface GithubConnectionConfig { */ archived?: boolean; /** - * Exclude private repositories from syncing. Internal repositories are not affected. + * You can exclude private repositories from syncing. Internal repositories are not affected. */ private?: boolean; /** diff --git a/packages/schemas/src/v3/github.schema.ts b/packages/schemas/src/v3/github.schema.ts index 7f63d7ccf..946774622 100644 --- a/packages/schemas/src/v3/github.schema.ts +++ b/packages/schemas/src/v3/github.schema.ts @@ -123,7 +123,7 @@ const schema = { "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/packages/schemas/src/v3/github.type.ts b/packages/schemas/src/v3/github.type.ts index 73195f67e..3a6922d5a 100644 --- a/packages/schemas/src/v3/github.type.ts +++ b/packages/schemas/src/v3/github.type.ts @@ -53,7 +53,7 @@ export interface GithubConnectionConfig { */ archived?: boolean; /** - * Exclude private repositories from syncing. Internal repositories are not affected. + * You can exclude private repositories from syncing. Internal repositories are not affected. */ private?: boolean; /** diff --git a/packages/schemas/src/v3/index.schema.ts b/packages/schemas/src/v3/index.schema.ts index c39337979..7466bf73f 100644 --- a/packages/schemas/src/v3/index.schema.ts +++ b/packages/schemas/src/v3/index.schema.ts @@ -624,7 +624,7 @@ const schema = { "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array", diff --git a/packages/schemas/src/v3/index.type.ts b/packages/schemas/src/v3/index.type.ts index f152649e2..4fd4efb74 100644 --- a/packages/schemas/src/v3/index.type.ts +++ b/packages/schemas/src/v3/index.type.ts @@ -293,7 +293,7 @@ export interface GithubConnectionConfig { */ archived?: boolean; /** - * Exclude private repositories from syncing. Internal repositories are not affected. + * You can exclude private repositories from syncing. Internal repositories are not affected. */ private?: boolean; /** diff --git a/schemas/v3/github.json b/schemas/v3/github.json index 782498d9a..2ddbbb3d6 100644 --- a/schemas/v3/github.json +++ b/schemas/v3/github.json @@ -95,7 +95,7 @@ "private": { "type": "boolean", "default": false, - "description": "Exclude private repositories from syncing. Internal repositories are not affected." + "description": "You can exclude private repositories from syncing. Internal repositories are not affected." }, "repos": { "type": "array",