block / block/buzz

Transfer a channel from one workspace (community) to another

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

**Motivation**

Operators who run more than one Buzz workspace routinely create a channel in the wrong one, or later decide a live channel belongs with a different team. Today there is no supported way to move that channel. The only workaround is delete-and-recreate, which breaks the channel UUID, message/thread continuity, canvas, memberships, bound git repos/projects, and any agent sessions keyed to the original `h` tag.

This showed up as a product request from a multi-workspace operator (Litbox nest: hosted community plus other workspaces). The same gap is also the recovery path for “I created this channel while the wrong community was active,” which [#6363](https://github.com/block/buzz/issues/6363) already documents as a real Desktop race.

**Proposed solution**

An owner-authorized **channel transfer** between workspaces (communities):

1. Actor is a channel owner (or community owner — maintainer call) on the source, and an owner/admin on the destination.
2. Desktop (and CLI) flow: pick destination workspace → dry-run → confirm → transfer.
3. Dry-run reports, before any write:
- destination UUID collision (`channels` PK is `(community_id, id)`; the same UUID *may* already exist there);
- members who are not members of the destination community;
- bound repos, projects, workflows, canvas, and attachments that will or will not come along;
- agents on the roster that are not visible in the destination ([#5770](https://github.com/block/buzz/issues/5770)).
4. Preserve the channel UUID, messages, threads, reactions, canvas, topic/purpose, and memberships that are legal in the destination. Do not auto-join people or agents to the destination community.
5. Write an immutable, channel-visible audit record (actor, source community, dest community, dry-run summary, timestamp).
6. **v1 scope:** same relay / same operator, two communities. Cross-relay (hosted ↔ self-hosted, or two hosts) is a different problem and should reuse the export/restore contract in [#5731](https://github.com/block/buzz/issues/5731) rather than a live re-tenant.

This is the “explicitly modeled admission path” the schema already reserves and does not implement:

```sql
-- schema/schema.sql
-- Migration lint forbids channel re-tenanting except through an
-- explicitly modeled admission path. We have no such path, so: hard block.
```

`docs/multi-tenant-relay.md` (P-RESOLVE) says the same: `channels.community_id` is immutable after insert; any future re-tenanting is a separate axiomatic admission with its own audit discipline and re-verification of S1/S2 (and I1–I5). This issue is a request to design that admission, not to `UPDATE channels SET community_id = …`.

**Alternatives considered**

| Approach | Why it is not enough |
|---|---|
| Delete and recreate the channel | Loses UUID, history, canvas, bindings, agent session identity. |
| Whole-workspace export/restore ([#5731](https://github.com/block/buzz/issues/5731)) | Right tool for disaster recovery and host migration; too coarse for “move this one channel.” Complementary, not a duplicate. |
| Slack-style multi-workspace *share* (channel visible in two workspaces) | Different product. Buzz’s host-derived tenant fence (`resolve(h)` must equal `req.community`) is the opposite of a shared channel. Transfer is a move, not a share. |
| Manual event replay / importer ([#5512](https://github.com/block/buzz/issues/5512), [#2704](https://github.com/block/buzz/pull/2704)) | Built for Slack→Buzz, not Buzz→Buzz, and cannot rewrite `community_id` from a client. |

**Non-goals**

- Sharing one channel across two live workspaces.
- Transferring agent in-memory session state or provider credentials.
- Auto-provisioning destination relay membership for people or agents who are not already there.
- Cross-relay live move in v1.
- Widening ordinary community-owner power over healthy channels they do not own ([#2928](https://github.com/block/buzz/issues/2928) stays a same-community ownership-recovery issue).

**Additional context**

Verified at `block/buzz` `596992258b3b2c8ca3061fcbc51884705078b644`:

- Trigger `trg_channels_community_id_immutable` in `schema/schema.sql` rejects any `community_id` change.
- Conformance table: `docs/multi-tenant-conformance.md` row “Channels and channel membership”.
- Isolation model: `docs/multi-tenant-relay.md` P-RESOLVE (`!ChannelCommunity` / `ChannelCommunity` CONSTANT).
- Events, members, workflows, search, Redis fan-out, media, and git pointers are all `community_id`-fenced, so a transfer has to move or rewrite those rows in one admission, not only the `channels` row.

**Duplicate check**

Searched open issues/PRs on 2026-09-01 for `transfer channel`, `move channel`, `re-tenant`, `workspace`. Closest, none of which cover a single-channel workspace-to-workspace move:

- [#5731](https://github.com/block/buzz/issues/5731) — owner-controlled **workspace** export/restore
- [#2928](https://github.com/block/buzz/issues/2928) — orphaned channel **ownership** inside one community
- [#6363](https://github.com/block/buzz/issues/6363) — create_channel can land in the wrong community (makes transfer the recovery)
- [#5512](https://github.com/block/buzz/issues/5512) / [#2704](https://github.com/block/buzz/pull/2704) — Slack→Buzz import, not Buzz→Buzz

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.