block / block/buzz

Desktop Projects: SSH clone URLs (git@host:owner/repo.git) are classified as "unresolved", so Fetch errors with "Repository unavailable"

Open
#7,181 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.