From 6107465821d762a1704e3ac2d2aa2a57633b21b1 Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Sat, 22 Aug 2026 23:35:02 -0400 Subject: [PATCH 1/3] fix(server): detect GitHub Enterprise remotes that gh is signed in to A GitHub Enterprise host is named by whoever installed it, so matching on a "github" DNS label misses installs like git.example.edu. Those remotes were classified as unknown, which resolves to a stub that fails every operation, so the PR panel stayed empty with "No unknown source control provider is registered." GitLab already claimed unknown hosts through refineUnknownRemote; GitHub never implemented it, so the discovery spec was filtered out and gh was never asked. Add the GitHub refiner so any authenticated gh host claims its remote. Matching accepts any signed-in account on the host rather than only the active one, since gh selects an active account per host and a remote should resolve regardless of which login is currently selected. The remote host is compared verbatim, port included, mirroring the GitLab refiner: refinement takes the first provider that claims a remote, so matching on a bare hostname could beat another CLI's exact host:port match and route operations through the wrong provider. --- .../GitHubSourceControlProvider.test.ts | 125 ++++++++++++++++++ .../GitHubSourceControlProvider.ts | 27 ++++ .../SourceControlProviderRegistry.test.ts | 48 +++++++ packages/shared/src/sourceControl.test.ts | 12 ++ 4 files changed, 212 insertions(+) diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts index 1381271e6bbc..307a05e8b213 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts @@ -396,3 +396,128 @@ it("reports an update hint instead of unauthenticated when gh predates --json", /2\.81\.0/, ); }); + +const authStatusJson = ( + accounts: ReadonlyArray<{ + readonly host: string; + readonly login: string; + readonly state?: string; + readonly active?: boolean; + }>, +): string => + JSON.stringify({ + hosts: Object.fromEntries( + accounts.map((account) => [ + account.host, + [ + { + state: account.state ?? "success", + active: account.active ?? false, + host: account.host, + login: account.login, + tokenSource: "keyring", + gitProtocol: "https", + }, + ], + ]), + ), + }); + +const refineUnknownRemote = (input: { readonly host: string; readonly stdout: string }) => + GitHubSourceControlProvider.discovery.refineUnknownRemote?.({ + cwd: "/repo", + context: { + provider: { + kind: "unknown", + name: input.host, + baseUrl: `https://${input.host}`, + }, + remoteName: "origin", + remoteUrl: `https://${input.host}/org/repo.git`, + }, + auth: processResult(input.stdout), + }); + +it("refines unknown remotes that gh is signed in to, whether or not the account is active", () => { + assert.deepStrictEqual( + refineUnknownRemote({ + host: "git.example.edu", + stdout: authStatusJson([ + { host: "github.com", login: "active-user", active: true }, + { host: "git.example.edu", login: "enterprise-user" }, + ]), + }), + { + kind: "github", + name: "GitHub Self-Hosted", + baseUrl: "https://git.example.edu", + }, + ); +}); + +it("leaves unknown remotes alone when gh has no working account for the host", () => { + assert.strictEqual( + refineUnknownRemote({ + host: "git.example.edu", + stdout: authStatusJson([ + { host: "git.example.edu", login: "enterprise-user", state: "failure" }, + ]), + }), + null, + ); + + assert.strictEqual( + refineUnknownRemote({ + host: "git.example.edu", + stdout: authStatusJson([{ host: "github.com", login: "active-user", active: true }]), + }), + null, + ); +}); + +it("refuses a port-bearing remote when gh only knows the bare hostname", () => { + // One hostname can serve two forges on separate ports. Refinement takes the first + // provider that claims the remote, so a bare-hostname match here would beat another + // CLI's exact host:port match and route operations through the wrong provider. + assert.strictEqual( + refineUnknownRemote({ + host: "git.example.edu:8443", + stdout: authStatusJson([{ host: "git.example.edu", login: "enterprise-user" }]), + }), + null, + ); +}); + +it("refines port-bearing remotes when gh reports the same host and port", () => { + assert.deepStrictEqual( + refineUnknownRemote({ + host: "git.example.edu:8443", + stdout: authStatusJson([{ host: "git.example.edu:8443", login: "enterprise-user" }]), + }), + { + kind: "github", + name: "GitHub Self-Hosted", + baseUrl: "https://git.example.edu:8443", + }, + ); +}); + +it("ignores stderr noise when refining, so gh warnings do not break host matching", () => { + const provider = GitHubSourceControlProvider.discovery.refineUnknownRemote?.({ + cwd: "/repo", + context: { + provider: { + kind: "unknown", + name: "git.example.edu", + baseUrl: "https://git.example.edu", + }, + remoteName: "origin", + remoteUrl: "https://git.example.edu/org/repo.git", + }, + auth: processResult(authStatusJson([{ host: "git.example.edu", login: "enterprise-user" }]), { + stderr: "warning: ignored diagnostic from gh\n", + }), + }); + + assert.strictEqual(provider?.kind, "github"); +}); diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts index 3dcc8ab826a6..45af8938b2af 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts @@ -18,6 +18,7 @@ import { providerAuth, type SourceControlAuthProbeInput, type SourceControlCliDiscoverySpec, + type SourceControlUnknownRemoteRefinementInput, } from "./SourceControlProviderDiscovery.ts"; function toChangeRequest(summary: GitHubCli.GitHubPullRequestSummary): ChangeRequest { @@ -92,6 +93,31 @@ function parseGitHubAuth(input: SourceControlAuthProbeInput) { }); } +// A GitHub Enterprise host is named by whoever installed it, so hostname guessing misses +// installs like `git.example.edu`. When detection lands on `unknown`, let a host `gh` is +// signed in to claim the remote. Any signed-in account counts rather than only the host's +// active one, so a second login on the same host still resolves the remote; whether `gh` +// knows the host at all is the question, not which account is currently selected. +function refineUnknownGitHubRemote(input: SourceControlUnknownRemoteRefinementInput) { + // Compare the remote host verbatim, port included. A host serving two forges on separate + // ports must not be claimed off a bare-hostname match, since refinement takes the first + // provider that claims the remote and would beat another CLI's exact match. + const host = input.context.provider.name.toLowerCase(); + const authenticated = parseGitHubAuthStatus(input.auth.stdout).accounts.some( + (account) => account.authenticated && account.host === host, + ); + + if (!authenticated) { + return null; + } + + return { + kind: "github", + name: "GitHub Self-Hosted", + baseUrl: input.context.provider.baseUrl, + } as const; +} + export const discovery = { type: "cli", kind: "github", @@ -100,6 +126,7 @@ export const discovery = { versionArgs: ["--version"], authArgs: ["auth", "status", "--json", "hosts"], parseAuth: parseGitHubAuth, + refineUnknownRemote: refineUnknownGitHubRemote, installHint: "Install the GitHub command-line tool (`gh`) via https://cli.github.com/ or your package manager (for example `brew install gh`).", } satisfies SourceControlCliDiscoverySpec; diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts index 54038502bfde..89b0e41cfdc4 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.test.ts @@ -203,6 +203,54 @@ self-hosted.example.test }), ); +it.effect("routes authenticated GitHub Enterprise remotes without relying on host naming", () => + Effect.gen(function* () { + const registry = yield* makeRegistry({ + remotes: [{ name: "origin", url: "https://git.example.edu/org/repo.git" }], + process: { + run: () => + Effect.succeed( + processOutput( + JSON.stringify({ + hosts: { + "github.com": [ + { + state: "success", + active: true, + host: "github.com", + login: "active-user", + tokenSource: "keyring", + gitProtocol: "https", + }, + ], + "git.example.edu": [ + { + state: "success", + active: false, + host: "git.example.edu", + login: "enterprise-user", + tokenSource: "keyring", + gitProtocol: "https", + }, + ], + }, + }), + ), + ), + }, + }); + + const handle = yield* registry.resolveHandle({ cwd: "/repo" }); + + assert.strictEqual(handle.provider.kind, "github"); + assert.deepStrictEqual(handle.context?.provider, { + kind: "github", + name: "GitHub Self-Hosted", + baseUrl: "https://git.example.edu", + }); + }), +); + it.effect("refines the caller-selected remote instead of choosing another configured remote", () => Effect.gen(function* () { const registry = yield* makeRegistry({ diff --git a/packages/shared/src/sourceControl.test.ts b/packages/shared/src/sourceControl.test.ts index 86b1ba5912bd..ebe2cb8ba4d6 100644 --- a/packages/shared/src/sourceControl.test.ts +++ b/packages/shared/src/sourceControl.test.ts @@ -122,6 +122,18 @@ describe("detectSourceControlProviderFromRemoteUrl", () => { ).toBe("unknown"); }); + it("leaves enterprise hosts without a provider label unknown for CLI refinement", () => { + // GitHub Enterprise hosts are named by the customer, so hostname matching cannot see + // them. Detection reports `unknown` and the provider CLIs settle it during refinement. + const remoteUrl = "https://git.example.edu/org/repo.git"; + + expect(detectSourceControlProviderFromRemoteUrl(remoteUrl)).toEqual({ + kind: "unknown", + name: "git.example.edu", + baseUrl: "https://git.example.edu", + }); + }); + it("detects SSH remotes with non-git SSH users (e.g. gitlab@, deploy@)", () => { expect( detectSourceControlProviderFromRemoteUrl("gitlab@gitlab.example.com:group/project.git")?.kind, From 42be8a7c6c9256bcb55bbbdb9ed8645afb68082f Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Sat, 22 Aug 2026 23:35:03 -0400 Subject: [PATCH 2/3] docs(user): note GitHub Enterprise Server support Signing in with gh --hostname is now enough for T3 Code to recognize an enterprise install, whatever the host is called. --- docs/user/source-control.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user/source-control.md b/docs/user/source-control.md index 916536bbe736..73432e73d748 100644 --- a/docs/user/source-control.md +++ b/docs/user/source-control.md @@ -79,6 +79,10 @@ Run a quick **Rescan** after setting up a new machine or changing credentials. You can now clone, publish, and create pull requests. +**GitHub Enterprise Server** works the same way, whatever your install is called. Sign in with +`gh auth login --hostname git.example.com`, and T3 Code recognizes projects on that host as GitHub +even when the hostname says nothing about GitHub. + ### For GitLab 1. Install the GitLab CLI: From 9d8c8da0333355ea9bdf5fc35a5ad7e8db6d7303 Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Wed, 7 Oct 2026 01:33:42 -0400 Subject: [PATCH 3/3] fix(server): restore GitHub Enterprise remote refinement after merge Merging main's new makeDiscovery managed-cli wrapper for GitHub silently dropped the self-hosted remote refinement this PR adds, stubbing refineUnknownRemote to always return null. Have it probe gh auth status and delegate to refineUnknownGitHubRemote, same as the plain CLI spec. --- .../GitHubSourceControlProvider.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts index 1174d85d60db..4684b41c790e 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts @@ -285,7 +285,28 @@ export const makeDiscovery = Effect.gen(function* () { }, } satisfies SourceControlProviderDiscoveryItem; }), - refineUnknownRemote: () => Effect.succeed(null), + refineUnknownRemote: Effect.fn("GitHubSourceControlProvider.refineUnknownRemote")( + function* (input: { + readonly cwd: string; + readonly context: SourceControlProvider.SourceControlProviderContext; + }) { + const auth = yield* process + .run({ + operation: "source-control.discovery.refine-unknown-remote", + command: discovery.executable, + args: discovery.authArgs, + cwd: input.cwd, + allowNonZeroExit: true, + timeoutMs: 5_000, + maxOutputBytes: 8_000, + appendTruncationMarker: true, + }) + .pipe(Effect.orElseSucceed(() => null)); + return auth === null + ? null + : refineUnknownGitHubRemote({ cwd: input.cwd, context: input.context, auth }); + }, + ), } satisfies SourceControlManagedCliDiscoverySpec; });