musubi makes several machines’ copies of one jj repository behave like workspaces of a single repository. This page explains what moves between machines, what stays on each, and what you see when machines change the same thing at once. For the design behind it, see docs/internals/design.md, and for how it’s checked, How musubi keeps your work safe.

The short version

  • Every machine has the whole repository. Each machine’s copy is an ordinary jj repository, with every commit, bookmark and change from every machine. You can unplug any of them and keep working. Other machines’ working copies show up in it as workspaces, such as laptop/default@: names for the commit each is on, not bookmarks, so nothing of theirs is ever pushed unless you push it.
  • Your work moves when jj records it. jj records your edits each time you run a jj command, and musubi sends each record to the other machines within a second or so. Edits you’ve made since your last jj command stay on that machine until the next one, or until jj records them by itself.
  • Your files are never touched. musubi never writes to your working copy. When another machine rewrites the commit you’re on, jj says so, and you update your files when you choose.
  • You still push and fetch. musubi never talks to GitHub or any other remote for you. When you push or fetch on one machine, though, every machine sees the result.
  • The hub is a meeting point, not a master. It keeps a copy of each repository it serves, in its directory on its machine, and decides the order in which machines’ work lands. Other machines reach it over ssh, with a key that can reach the hub and nothing else on its machine. It holds nothing the machines don’t have too. Anyone who can reach the hub can read and write every repository it serves, so treat it as you would any machine with your repositories on it.
  • Leaving costs nothing. musubi leave takes a repository out of its hive, and it keeps everything it has, its working copy included.

Hubs, hives, and machines

flowchart LR
  subgraph devbox["devbox"]
    H["musubi hub"]
    D1["daemon"] --- R1["~/src/app"]
  end
  subgraph laptop["laptop"]
    D2["daemon"] --- R2["~/src/app"]
  end
  subgraph desktop["desktop"]
    D3["daemon"] --- R3["~/code/app"]
  end
  D1 <-->|local socket| H
  D2 <-->|ssh| H
  D3 <-->|ssh| H
  • A hub is a musubi hub process on a machine that is usually on. It keeps a full copy of each repository it serves and decides the order in which updates are accepted. It never makes changes of its own.
  • A hive is one repository as the hub knows it: a name, such as app, and every machine’s copy of it. One hub serves any number of hives.
  • Each machine runs one daemon, which keeps every copy on that machine in sync with its hive. The hub’s machine can have copies too, as devbox does above.

Other machines reach the hub over ssh, so wherever you can ssh devbox, musubi works. The daemon reconnects by itself after a network change or a sleep.

What moves between machines

Every jj command that changes the repository records what it did as an operation, in jj’s operation log: the record that jj undo takes back and jj op log shows. musubi syncs that record, not just branches. The daemon sends each new operation, with the commits it needs, to the hub, which passes it on to the other machines, within a second or so. So every machine has not only the same commits, but the same history of how they got there.

Shared across the hiveEach machine’s own
Commits, including ones no bookmark points toWorking-copy files
Bookmarks, tags, and remote bookmarksUntracked and ignored files
The operation logUser config (~/.config/jj/config.toml)
Each machine’s working-copy commit, shown as a workspaceMost repo config
Git remotes: their names and URLsThe commit index, which jj rebuilds
Repo config that decides what is safe to rewrite or push

Share settings says which remotes and repo config the hive shares, and how machines agree on them.

Every snapshot of a working copy replicates too, so a file that jj snapshots reaches every machine in the hive, even if you abandon it at once. Keep secrets in ignored files.

Because that history is shared, the rest of jj’s behaviour carries across machines:

  • jj op log shows what every machine did, and when.
  • jj evolog follows a change through rewrites made anywhere.
  • jj op revert and jj op restore work on operations from any machine. jj undo refuses to undo another machine’s operation unless you pass --allow-cross-workspace, as it refuses another workspace’s.
  • A fetch or push on one machine updates the remote bookmarks on every machine, so you only need to fetch once.

Working copies stay put

musubi never writes to working-copy files. It changes only what jj keeps (the operation log and commits, and in a colocated repository, the .git refs that track bookmarks), and the git remotes and repo config that the hive shares.

When another machine rewrites the commit your working copy is on, by rebasing its parent, say, jj treats your working copy as stale, as it would after a rebase in another workspace on the same machine, and your next jj command stops until you update the files. When another machine rewrites your working copy says what to do, and why musubi doesn’t make jj do it by itself.

Other machines’ workspaces

Every machine has its own workspaces, its own default@ among them, and also shows every other machine’s working copy as a workspace named after that machine:

@  vruxwmqv me@example.com 2026-10-23 10:14:02 default@ 3f2a1c9e
│  (empty) (no description set)
│ ○  kkmpptxz me@example.com 2026-10-23 10:12:40 laptop/default@ 8d0b4e11
├─╯  Add retry logic to the fetcher
◆  zzzzzzzz root() 00000000

laptop/default@ is the commit the laptop last snapshotted: edits it hasn’t snapshotted yet stay on the laptop, as they would in any workspace. You can diff it or build on it like any revision; see See what other machines are doing.

A machine’s name defaults to jj’s operation.hostname; choose another with --machine when you join. A machine that leaves with musubi leave, or that another machine forgets with musubi forget, stops showing, and comes back if it’s used again. See Take a repository or machine out.

When machines change the same thing

Changes made on different machines combine exactly as jj combines changes made by concurrent jj commands on one machine, and every machine ends up with the same result. A bookmark moved to different commits on two machines is conflicted everywhere, and a change rewritten on two machines is divergent everywhere, until you resolve it once, on any machine. Work across machines says how.

When two machines send updates at once, the hub accepts the first and turns the other away; that machine merges its update with the first and sends the result. So each merge happens once, on the machine that made the later change, and the hive never gets two copies of the same rebase. docs/internals/races.md lists every case musubi tests, with its outcome.

Offline machines

A machine that loses its connection to the hub keeps working: jj never needs the hub. When it reconnects, its daemon fetches what it missed, merges its own work with it, and sends the result, as for any concurrent change. Meanwhile, musubi status shows its syncs failing, which is expected.

Disk space

Every machine keeps every operation and commit of its hive, so the daemon cleans up what jj util gc would: operations that nothing reachable from the current one needs, and files jj no longer reads, none less than two weeks old. It runs once enough has built up, at most once a week, and not on battery. Unlike jj util gc, it deletes no git object.

Moving and copying repositories

A copy is the repository that joined, wherever you move it: the daemon follows it. A repository made by copying a copy’s files is a duplicate, which musubi doesn’t sync, since two repositories syncing as one would overwrite each other’s working copy. See Move, copy or restore a repository, and for the cases musubi handles and leaves out, docs/internals/copies.md.

Operation ids differ between machines

Each machine records its own git refs and workspaces in its operations, so the same operation has a different id on each machine and on the hub. Anything that names an operation, such as jj op restore <id>, takes this machine’s id. To find the same operation on another machine, run musubi debug op-id <id> here, which prints the hive’s id for it, then musubi debug op-id with that id on the other machine.

The hub needn’t be durable

Every machine has every operation it has seen, so a hub holds nothing that isn’t on the machines too. A hub that loses its state, through a power cut or a deleted directory, loses no work: start a new one, and make its hives again from the machines’ copies. See Replace or move a hub.