Desktop Projects: SSH clone URLs (git@host:owner/repo.git) are classified as "unresolved", so Fetch errors with "Repository unavailable"
- Dominant language
- Rust
- Stars
- 32.7k
- Forks
- 4.3k
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 253
Description
**Describe the bug**
A repository announcement may advertise a `clone` tag in git's SCP-like SSH
syntax, for example `git@github.com:owner/repo.git`. That is the string
`git remote add` accepts and the string GitHub shows under "SSH".
`projectRepoHost()` classifies that remote with `new URL(cloneUrl)`. The WHATWG
URL parser rejects the SCP form, because `git@github.com` is not a valid scheme.
The `catch` branch then returns `{ kind: "unresolved" }`.
https://github.com/block/buzz/blob/main/desktop/src/features/projects/lib/projectRepoHost.ts#L19-L31
`unresolved` is not `external`, so every external-repository code path stays off:
- `useProjectRepoPresentation` sets `remoteKind: undefined`, so
`RepoSyncActionButton` does not take its `remoteKind === "external"` branch. It
renders **Fetch** instead of **Open**.
- `repositoryDisplayPath()` throws on the same `new URL` call and returns `null`,
so the header shows no repository location.
- The Projects list filter `repositoryScope === "linked"` never matches the
repository.
The visible failure is the Fetch button. `handleFetchRepo` refetches three
queries unconditionally:
https://github.com/block/buzz/blob/main/desktop/src/features/projects/ui/ProjectDetailScreen.tsx#L326-L331
`useProjectRepoSnapshotQuery` and `useProjectRepoSyncStatusQuery` are both gated
on a Buzz-hosted remote through `enabled`, but TanStack Query v5 `refetch()`
calls `query.fetch()` directly and does not consult `enabled`. Both Tauri
commands therefore run and both call `validate_workspace_clone_url`, which fails
on the SCP URL with `invalid clone URL: relative URL without a base`.
`projectRepoUnavailableReason` classifies that as `unknown`, so the user gets
the toast **"Repository unavailable — Buzz could not load this repository. Try
again or contact the project owner."**
**Steps to reproduce**
1. Announce a repository whose `clone` tag is an SCP-style SSH URL:
```
["d","hike-with-axe"]
["clone","git@github.com:dkeysil/hike-with-axe.git"]
["web","https://github.com/dkeysil/hike-with-axe"]
```
2. Open the project in Desktop → Files or Reviews.
3. The header offers **Fetch** rather than **Open on github.com**, and the
repository location is blank.
4. Click **Fetch**.
5. The toast "Repository unavailable" appears.
**Expected behavior**
An SSH remote is still an external remote. Buzz should classify
`git@host:owner/repo.git` as `{ kind: "external", host }`, show **Open**, show
`github.com/owner/repo` as the location, and include the repository under the
"Linked" filter. Fetch must not invoke relay-only commands for a remote that is
not hosted on the workspace relay.
**Version and platform**
- Buzz version: 0.5.20 (reproduced on `main` @ `114dbf745`)
- OS: Arch Linux, Hyprland (Wayland). The defect is platform independent — it is
URL parsing in the shared desktop frontend.
**Logs / additional context**
The parser behaviour, in a browser or Node console:
```js
new URL("git@github.com:dkeysil/hike-with-axe.git")
// TypeError: Invalid URL
new URL("ssh://git@github.com/dkeysil/hike-with-axe.git").host
// "github.com"
```
Suggested fix — normalize the SCP form before parsing, in `projectRepoHost.ts`,
so `projectRepoHost` and `repositoryDisplayPath` both benefit:
```ts
function normalizeGitRemoteUrl(cloneUrl: string): string {
const scp = /^([^\s/@]+@)?([^\s/:]+):(?!\/)(.+)$/.exec(cloneUrl);
return scp ? `ssh://${scp[1] ?? ""}${scp[2]}/${scp[3]}` : cloneUrl;
}
```
Two follow-ons are needed with it:
1. `canCloneLocally` in `useProjectRepoHost.ts` currently accepts any external
`github.com` host. The Rust gate `validate_local_clone_url` only accepts
`https://github.com/owner/repo`, so the check must also require an HTTPS
clone URL. Otherwise the newly reachable Clone action offers a clone that
Rust rejects.
2. `handleFetchRepo` must skip `repoSnapshotQuery` and `repoSyncStatusQuery`
whenever the remote is not Buzz-hosted, because `refetch()` bypasses
`enabled`. Refreshing the repo state event is the only remote read that
applies to an external remote.
I have this change working locally against `822c5ab23` with unit tests, and the
full desktop suites pass (5457 JS, 2872 Rust). Happy to open a PR.
Related: #7174, #7175 — same Projects area, different root causes.
Contributor guide
Assessment
This issue has not been assessed yet.