From 840e6d5f15d719575bde78d9dee6a9cd25e9160c Mon Sep 17 00:00:00 2001 From: Michael Isaac <217397473+MPIsaac-Per@users.noreply.github.com> Date: Thu, 27 Aug 2026 07:04:56 -0500 Subject: [PATCH] Warn when an extraRepos entry cannot track a repository The GitHub plugin's extraRepos setting filtered its entries through isRepoName and dropped everything that failed, with no log line and no error. A typo or a wildcard such as "owner/*" was therefore indistinguishable from a setting that worked: the panel simply showed the repos discovered from BB project checkouts. Split the parse into valid and invalid entries and warn for each entry that cannot become a repository, naming the entry and the expected owner/repo shape. --- plugins/github/server.rpc.test.ts | 17 ++++++++++++++++ plugins/github/server.test.ts | 20 ++++++++++++++++++ plugins/github/server.ts | 34 +++++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/plugins/github/server.rpc.test.ts b/plugins/github/server.rpc.test.ts index 9bf668fb76..742674cb92 100644 --- a/plugins/github/server.rpc.test.ts +++ b/plugins/github/server.rpc.test.ts @@ -348,4 +348,21 @@ describe("github plugin RPC behavior", () => { }, }); }); + it("warns about an extraRepos entry that cannot track anything", async () => { + const host = createFakePluginHost({ + pluginId: "github", + settings: { extraRepos: "acme/widgets, ACME/*" }, + }); + await plugin(host.bb); + + // The valid sibling still syncs; the glob contributes no repo. + await expect(host.harness.callRpc("refresh")).resolves.toMatchObject({ + repos: 1, + }); + expect( + host.harness.logEntries.filter( + (entry) => entry.level === "warn" && entry.message.includes("ACME/*"), + ), + ).not.toHaveLength(0); + }); }); diff --git a/plugins/github/server.test.ts b/plugins/github/server.test.ts index cbe21f132e..bad0838f90 100644 --- a/plugins/github/server.test.ts +++ b/plugins/github/server.test.ts @@ -6,6 +6,7 @@ import { fetchRepoItems, githubRpcContract, parsePaginatedGhApi, + parseExtraRepos, validateGithubCliArgs, } from "./server"; @@ -141,6 +142,25 @@ describe("GitHub RPC contract", () => { ); }); + it("separates trackable extraRepos entries from wildcards and typos", () => { + expect(parseExtraRepos("get-bb/bb, owner/other")).toEqual({ + valid: ["get-bb/bb", "owner/other"], + invalid: [], + }); + // A glob parsed to nothing at all, with no warning and no tracked repo. + expect(parseExtraRepos("get-bb/*")).toEqual({ + valid: [], + invalid: ["get-bb/*"], + }); + expect(parseExtraRepos("get-bb/bb, get-bb/*, nope")).toEqual({ + valid: ["get-bb/bb"], + invalid: ["get-bb/*", "nope"], + }); + // Blank and separator-only settings are empty, not invalid. + expect(parseExtraRepos("")).toEqual({ valid: [], invalid: [] }); + expect(parseExtraRepos(" , ")).toEqual({ valid: [], invalid: [] }); + }); + it("infers parsed handler inputs and frontend results", () => { expectTypeOf< Parameters[0] diff --git a/plugins/github/server.ts b/plugins/github/server.ts index 4b26ba1563..5d0d6cffdb 100644 --- a/plugins/github/server.ts +++ b/plugins/github/server.ts @@ -345,6 +345,25 @@ function isRepoName(value: unknown): value is string { return typeof value === "string" && /^[\w.-]+\/[\w.-]+$/.test(value); } +/** + * Split the extraRepos setting into repository names this plugin can track and + * entries it cannot. The setting is an explicit list, so a wildcard such as + * "owner/*" is invalid rather than an owner-wide match. + */ +export function parseExtraRepos(raw: string): { + valid: string[]; + invalid: string[]; +} { + const valid: string[] = []; + const invalid: string[] = []; + for (const entry of raw.split(/[\s,]+/)) { + if (entry.length === 0) continue; + if (isRepoName(entry)) valid.push(entry); + else invalid.push(entry); + } + return { valid, invalid }; +} + function run( file: string, args: string[], @@ -609,10 +628,17 @@ export default async function plugin(bb: BbPluginApi) { ); } const { extraRepos } = await settings.get(); - for (const raw of extraRepos.split(/[\s,]+/)) { - if (isRepoName(raw) && !byRepo.has(raw)) { - byRepo.set(raw, { repo: raw, projectId: null }); - } + const extra = parseExtraRepos(extraRepos); + for (const raw of extra.valid) { + if (!byRepo.has(raw)) byRepo.set(raw, { repo: raw, projectId: null }); + } + // An unusable entry used to be dropped in silence, so a typo or a glob + // looked identical to a setting that worked. + for (const bad of extra.invalid) { + bb.log.warn( + `extraRepos entry "${bad}" is not an owner/repo name and tracks nothing. ` + + "List each repository explicitly, for example \"owner/repo\".", + ); } const repos = [...byRepo.values()]; repoCache = { repos, fetchedAt: Date.now() };