From f7efb487c22a4233392c32cf8bdf3de2ad05f468 Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Sat, 22 Aug 2026 23:16:06 -0400 Subject: [PATCH 1/3] refactor(shared): export the remote host-name normalizer Provider CLIs report hosts inconsistently, so callers outside this module need the same port-stripping normalization that detection already uses. --- packages/shared/src/sourceControl.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/sourceControl.ts b/packages/shared/src/sourceControl.ts index df88de595a3f..820d0b02d92d 100644 --- a/packages/shared/src/sourceControl.ts +++ b/packages/shared/src/sourceControl.ts @@ -158,7 +158,12 @@ function parseRemoteHost(remoteUrl: string): string | null { } } -function parseHostName(host: string): string { +/** + * Normalizes a remote host to its bare hostname, dropping any port. Provider CLIs report + * hosts inconsistently — `gh` keys by bare hostname while a remote may carry `:8443` — so + * comparisons against CLI output go through here. + */ +export function parseHostName(host: string): string { try { return new URL(`https://${host}`).hostname.toLowerCase(); } catch { From 416f7c1519612c283101fb24be3caf23abb50542 Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Sat, 22 Aug 2026 23:16:19 -0400 Subject: [PATCH 2/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. Both sides of the host comparison are normalized so a remote carrying a port still matches gh's bare hostnames. --- .../GitHubSourceControlProvider.test.ts | 112 ++++++++++++++++++ .../GitHubSourceControlProvider.ts | 27 +++++ .../SourceControlProviderRegistry.test.ts | 48 ++++++++ packages/shared/src/sourceControl.test.ts | 12 ++ 4 files changed, 199 insertions(+) diff --git a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts index 1381271e6bbc..a6ccbc26d812 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.test.ts @@ -396,3 +396,115 @@ 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("refines unknown remotes on non-standard ports against gh's bare hostnames", () => { + assert.deepStrictEqual( + refineUnknownRemote({ + host: "git.example.edu:8443", + stdout: authStatusJson([{ host: "git.example.edu", 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..1546788271ae 100644 --- a/apps/server/src/sourceControl/GitHubSourceControlProvider.ts +++ b/apps/server/src/sourceControl/GitHubSourceControlProvider.ts @@ -7,6 +7,7 @@ import { type ChangeRequest, type ChangeRequestState, } from "@t3tools/contracts"; +import { parseHostName } from "@t3tools/shared/sourceControl"; import * as GitHubCli from "./GitHubCli.ts"; import { findAuthenticatedGitHubAccount, parseGitHubAuthStatus } from "./gitHubAuthStatus.ts"; @@ -18,6 +19,7 @@ import { providerAuth, type SourceControlAuthProbeInput, type SourceControlCliDiscoverySpec, + type SourceControlUnknownRemoteRefinementInput, } from "./SourceControlProviderDiscovery.ts"; function toChangeRequest(summary: GitHubCli.GitHubPullRequestSummary): ChangeRequest { @@ -92,6 +94,30 @@ 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) { + // Unknown contexts carry the raw remote host in `name`, which keeps any port, while `gh` + // keys its hosts by bare hostname. + const host = parseHostName(input.context.provider.name); + const authenticated = parseGitHubAuthStatus(input.auth.stdout).accounts.some( + (account) => account.authenticated && parseHostName(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 2640c9716c50d33aadb728136a629393a7d6716e Mon Sep 17 00:00:00 2001 From: Yahya Gilany Date: Sat, 22 Aug 2026 23:16:23 -0400 Subject: [PATCH 3/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: