The problem we hit
A repository whose remote is a self-hosted GitLab on a domain that does not contain gitlab — ssh://git@code.example.com:2222/group/project.git — has no forge at all in v0.15.0:
- No Create pull request in the status bar (the whole button, not a disabled one)
- View on remote, commit links, and
#N issue chips produce nothing
- The GitLab (self-hosted) account connected for that exact host in Settings → Accounts is never used for the repository
Environment: macOS 15 (darwin 24.6), v0.15.0, Tauri v2 / WKWebView.
Steps: connect a GitLab (self-hosted) account for code.example.com, open a repository whose remote is the URL above, check out a branch other than main, look at the status bar.
Where it comes from — packages/core/src/forge/remote.ts:
if (hostname.includes('github')) { … }
if (hostname === 'bitbucket.org') { … }
if (hostname.includes('bitbucket')) { … }
if (hostname.includes('gitlab')) { … }
return null;
That null then propagates quietly: webUrl() returns null for every kind, so currentPullRequestUrl is null and StatusBar renders nothing behind {prUrl && …}; buildBrowseUrl / buildFileUrl / buildCommitUrl / issueHref are null too. Nothing logs, so it reads as "the feature does not exist" rather than "your host was not recognized".
What it is not:
- Not auth. The account verifies fine —
account_check.rs already probes self-hosted GitLab by host (https://<host>/api/v4/personal_access_tokens/self, falling back to http and to /api/v4/user). The token works; the remote just never maps to that host's forge.
- Not the default-branch gate in
currentPullRequestUrl, and not the max-[950px]:hidden on the status bar action. It reproduces on a feature branch in a wide window.
- Not specific to the
ssh:// + port shape. parseRemote handles that correctly; https://code.example.com/group/project.git behaves the same.
Options we considered
| Option |
Why it might work |
Cost |
Widen the substring list (git., code., scm., …) |
Zero config |
Guesses, and cannot tell GitLab from Gitea or GitHub Enterprise on a same-shaped domain |
| Fall back to one kind when nothing matches |
One line; self-hosted GitLab is the common case, and GitLab's URL shapes (/-/blob/, /-/merge_requests/new) are what most unrecognized hosts want |
Gitea / Gogs / sr.ht get GitLab-shaped URLs; a GitHub Enterprise custom domain is read wrong |
Take the kind from the connected account's provider (gitlab-self, github, …) |
Accounts are already keyed by host, so the user has effectively declared it |
parseForgeRemote is pure and runs inside render paths, so the map has to reach it — a module-level registry seeded from settings, or a parameter threaded through every caller. Does nothing until an account is connected |
| Explicit host → kind map in Settings, plus a configurable default |
Deterministic, covers GitHub Enterprise and Gitea, no guessing |
New settings surface, and the same "how does a pure function see it" plumbing |
Probe the host (/api/v4/version vs the GitHub API) and cache the verdict |
No user input at all |
Turns a pure sync call into a network round-trip; VPN and captive portals make it flaky |
What we verified works
- On a real self-hosted instance (macOS, Tauri build). With the host resolved to GitLab instead of falling through to
null, the status bar action comes back on a feature branch and opens the correct prefilled page, https://<host>/<group>/<project>/-/merge_requests/new?merge_request%5Bsource_branch%5D=…. That is the direct confirmation that the null out of parseForgeRemote — not auth, not the branch gate — is what removes the action.
- Confirmed on the account side. The GitLab self-hosted token verifies against the same host the remote parser rejects, which is what makes the symptom confusing: the app reports the account as connected and still offers nothing on the repository.
- Traced, not bisected. That the same
null reaches buildBrowseUrl, buildFileUrl, buildCommitUrl, and issueHref comes from reading those paths, not from clicking each one.
- Not verified. Creating a merge request in-app through the API (the dialog path) on a self-hosted instance. We also have a local host → kind mapping in Settings with unit tests, but we are not putting it forward as a proven design — we are asking about direction, not asking you to take a patch.
Questions for the maintainer
- Is hostname-substring matching intended as the whole detection story, or was a host → kind mapping already on your list? Settings → Accounts already distinguishes
gitlab from gitlab-self per host, so the information exists one layer away.
- Would you take the connected account's
provider as the source of truth for an unrecognized host? If so, how should it reach parseForgeRemote without breaking its purity — a registry the settings layer seeds, or an explicit argument at the call sites?
- When nothing is configured and nothing matches, what should the default be: keep returning
null as today, assume GitLab, or assume GitHub? The silence is the part that reads as a missing feature rather than an unrecognized host.
- Is there a constraint that keeps
parseForgeRemote pure and synchronous that we should design around — the boot path, graph row rendering, or something else?
The problem we hit
A repository whose remote is a self-hosted GitLab on a domain that does not contain
gitlab—ssh://git@code.example.com:2222/group/project.git— has no forge at all in v0.15.0:#Nissue chips produce nothingEnvironment: macOS 15 (darwin 24.6), v0.15.0, Tauri v2 / WKWebView.
Steps: connect a GitLab (self-hosted) account for
code.example.com, open a repository whose remote is the URL above, check out a branch other thanmain, look at the status bar.Where it comes from —
packages/core/src/forge/remote.ts:That
nullthen propagates quietly:webUrl()returns null for every kind, socurrentPullRequestUrlis null andStatusBarrenders nothing behind{prUrl && …};buildBrowseUrl/buildFileUrl/buildCommitUrl/issueHrefare null too. Nothing logs, so it reads as "the feature does not exist" rather than "your host was not recognized".What it is not:
account_check.rsalready probes self-hosted GitLab by host (https://<host>/api/v4/personal_access_tokens/self, falling back to http and to/api/v4/user). The token works; the remote just never maps to that host's forge.currentPullRequestUrl, and not themax-[950px]:hiddenon the status bar action. It reproduces on a feature branch in a wide window.ssh://+ port shape.parseRemotehandles that correctly;https://code.example.com/group/project.gitbehaves the same.Options we considered
git.,code.,scm., …)/-/blob/,/-/merge_requests/new) are what most unrecognized hosts wantprovider(gitlab-self,github, …)parseForgeRemoteis pure and runs inside render paths, so the map has to reach it — a module-level registry seeded from settings, or a parameter threaded through every caller. Does nothing until an account is connected/api/v4/versionvs the GitHub API) and cache the verdictWhat we verified works
null, the status bar action comes back on a feature branch and opens the correct prefilled page,https://<host>/<group>/<project>/-/merge_requests/new?merge_request%5Bsource_branch%5D=…. That is the direct confirmation that thenullout ofparseForgeRemote— not auth, not the branch gate — is what removes the action.nullreachesbuildBrowseUrl,buildFileUrl,buildCommitUrl, andissueHrefcomes from reading those paths, not from clicking each one.Questions for the maintainer
gitlabfromgitlab-selfper host, so the information exists one layer away.provideras the source of truth for an unrecognized host? If so, how should it reachparseForgeRemotewithout breaking its purity — a registry the settings layer seeds, or an explicit argument at the call sites?nullas today, assume GitLab, or assume GitHub? The silence is the part that reads as a missing feature rather than an unrecognized host.parseForgeRemotepure and synchronous that we should design around — the boot path, graph row rendering, or something else?