block / block/buzz

Deep links die outside Buzz: add a self-hostable https:// front for buzz:// so links survive GitHub/Slack markdown sanitizers

Open
#7,100 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

## Problem

`buzz pr open`, `buzz issues create`, `buzz repos create` and the desktop "copy link" button all return `buzz://…` deep links. Those links are dead anywhere the markdown is rendered by someone other than Buzz — most importantly in **GitHub PR and issue bodies**, where we want a link from a GitHub PR back to the Buzz thread that originated it.

GitHub's markdown sanitizer only allow-lists `http`, `https`, `mailto` (and a couple of others) as `` schemes. A `[text](buzz://…)` link renders as plain text with the href dropped, and raw `` is stripped the same way. This is general GitHub behaviour, not something Buzz can fix on GitHub's side — see [github/community#27857](https://github.com/orgs/community/discussions/27857).

Live demonstration, in this very issue body — this link should render inert once GitHub is done with it:

[buzz://message?channel=3c9bffed-c8f3-581d-90e7-6009d03cd10f&id=5894f12222fee029a14c143ce0781e19279c94d5273dd85d0200bd7f3738bf4c](buzz://message?channel=3c9bffed-c8f3-581d-90e7-6009d03cd10f&id=5894f12222fee029a14c143ce0781e19279c94d5273dd85d0200bd7f3738bf4c)

The same applies to any other externally-rendered surface: Slack, Jira, Linear, a README, a static site.

## Why this is cheap to fix for Buzz specifically

Buzz deep links carry **no instance identity**. `crates/buzz-cli/src/links.rs` says so explicitly:

> The link chooses only the channel and event within the relay already configured for this CLI process. It cannot override the relay or identity.

And `desktop/src-tauri/src/deep_link.rs` confirms it in the parsers:

- `buzz://message?channel=&id=[&thread=]` — no relay parameter
- `buzz://channel/[/]` — path only, query rejected outright
- `buzz://repo|project|pr|issue?owner=&d=[&id=…][&tab=…][&commit=…]` — `parse_entity_deep_link` accepts *only* those keys

(The exceptions are `buzz://join?relay=wss://…&code=…` and `buzz://add-community?…`, which do carry a relay URL by design — those are the links that decide *which* instance you talk to.)

So for every navigation link, an `https://` front needs to do exactly one thing: **swap the scheme to `buzz:` and drop its own hostname**, preserving path and query byte-for-byte. No mapping table, no lookup, no state.

**Important constraint on any alternative design:** entity links are validated in strict canonical form — `parse_entity_deep_link` returns `None` on *any* unrecognised query parameter, deliberately, "so a future extension of the format is declined by old builds rather than silently misread". Adding e.g. a `relay=` parameter to entity links would therefore be rejected by every already-shipped build. A pure scheme swap avoids that entirely.

## Proposal

Serve the https front from the **relay itself**, not from a hard-coded `buzz.xyz` subdomain.

Add an unauthenticated route to `crates/buzz-relay/src/router.rs`, alongside the existing membership-gate-exempt invite routes:

```
GET /open/message?channel=…&id=…&thread=…
GET /open/pr?owner=…&d=…&id=…
GET /open/issue?owner=…&d=…&id=…
GET /open/repo?owner=…&d=…
GET /open/project?owner=…&d=…
GET /open/channel/[/]
```

Each returns a small HTML page that attempts `location.href = "buzz://"` and falls back to a visible "Open in Buzz / get Buzz" page when nothing handles the scheme. (An HTTP 302 straight to a custom scheme is not reliably followed by browsers; the interstitial page is the pattern Slack, Linear and VS Code use for exactly this.) On iOS and Android, Universal Links / App Links against the same host remove the interstitial hop entirely for installed clients.

The route holds no secrets and needs no auth — it only echoes back identifiers that are already in the URL the user pasted.

Then:

- `buzz pr open` / `buzz issues create` / `buzz repos create` / `buzz projects create` gain a second field next to `link` (e.g. `web_link`), derived from the relay's own public HTTP origin. `buzz://` stays canonical for in-app use; the https form is what you paste into a GitHub PR body.
- Desktop's "copy link" gets the same second option.

### Self-hosting is the requirement here, not a nice-to-have

We run a self-hosted deployment, and I expect we are not the only ones. A single `https://open.buzz.xyz/…` redirector would technically work for us — the links are instance-agnostic, so buzz.xyz would not need to know anything about our relay — but it would mean **every channel UUID, event id and repo d-tag we paste into a GitHub PR gets sent to a third-party host on click**. That is not acceptable for a private deployment, and it would make self-hosted installs functionally dependent on buzz.xyz uptime for a link format their own docs recommend.

So whatever ships, the https origin must come from the deployment's own configuration (relay public URL / an explicit `PUBLIC_WEB_ORIGIN`), with `buzz.xyz` at most the default for hosted installs. Note that `web/src/app/routes/` already exists and is already self-hosted alongside the relay — `/open` is a natural fit there if the web app is the better home than the relay crate.

## Alternatives considered

- **Get GitHub to allow-list the `buzz` scheme.** Not under anyone's control here, and GitHub has consistently declined general custom-scheme allow-listing (see the linked discussion).
- **Raw HTML `
` in the body.** Stripped by the same sanitizer.
- **Add a `relay=` parameter so a global redirector knows the instance.** Unnecessary (links are instance-agnostic) and actively harmful for entity links, which reject unknown parameters by design in every shipped build.
- **Paste the `buzz://` URL as bare text and let people copy it.** What we do today. It is not clickable, it is not obviously a link, and it does not survive being forwarded.

## Related

- #5857 — Desktop's own renderer drops `obsidian://` links; the mirror image of this problem, from the other side of the sanitizer.

Contributor guide

Open the contributing guide

Research direction

Start by reading crates/buzz-cli/src/links.rs and desktop/src-tauri/src/deep_link.rs to confirm the existing link formats, then inspect crates/buzz-relay/src/router.rs and web/src/app/routes/. Done means navigation links have self-hosted https forms, the unauthenticated /open routes preserve the specified path and query, and CLI and desktop surfaces expose the additional web link.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api, backend, cli, desktop
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.