Docs/Start/Using Zana on multiple machines

Using Zana on multiple machines

Use one Zana address, such as your-name.zana-ide.com, to work across your laptops. Your primary computer runs the shared Zana instance and stores its projects and history. A second machine can run work in its own project folders. Your browser connects to the primary, which routes work to the machine you select.

The primary must stay awake with Zana running. The second machine must also be awake and connected when you run work there. Opening the address from another device does not move the server to that device.

Multi-machine sharing is still being qualified. The current implementation supports machine pairing, shared desktop access, Threads, ordinary terminals, and files/Git on the selected machine. Public testing of recovery across two physical machines and rollout qualification remain open. CLI Agents and Squad/Team workers currently execute on the primary machine.

Choose what you want to do#

Your goal Setup
Control your existing Zana from another laptop or phone Open your Zana address in a browser and sign in.
Use the desktop app on a second laptop with the same projects and history Open the existing shared instance from Remote access.
Run Threads, terminals, or file/Git operations on the second laptop Pair it through Settings → Machines, then add its project folder.
Keep a separate Zana with its own projects and history Set up an independent instance with its own address.

You can use the second laptop both as a desktop client and as an execution machine. Opening the shared instance and pairing an execution machine are two separate steps. Installing Zana on both laptops alone does not merge their data.

Before you start#

  • Choose the primary computer that will remain running while you work remotely.
  • Update Zana on any computer where you use the desktop app. This guide describes the current implementation; older builds may not show the shared-instance or checkout controls.
  • On the second execution machine, have Node.js 22 or newer, Git, and the provider CLI you plan to use installed and signed in. The installer supports macOS and Linux hosts; it installs the background Zana host daemon.
  • Put a checkout of your repository on the second machine and note its absolute path, for example /Users/you/projects/my-app or /home/you/projects/my-app.
  • Use the same GitHub account when opening your Zana address or shared desktop instance. The machine itself joins using the installer generated by the primary.

The Connect setup works over the internet. Both laptops can be on different networks; it does not require shared Wi-Fi or router port forwarding.

1. Set up your remote address#

Do this in the desktop app on the primary computer. If your existing Zana address already opens correctly, continue to step 2.

  1. Open Settings → Remote access and click Get a connect code.
  2. Sign in with GitHub on the account page and generate a one-time code.
  3. Paste the code into Zana. Pairing enables remote access; wait for Connected.
  4. Click Choose your address. On the account page, use Pick your address to claim a name such as your-name.zana-ide.com for this primary instance.
  5. Return to Zana and use Copy address or Open Zana. Open that address in a browser, sign in with the same GitHub account, and choose Open Zana.

Check: the browser shows the primary computer's existing projects and history. Keep this address when adding the second machine.

This walkthrough uses a personal address under zana-ide.com. Using a domain you own, such as zana.example.com, requires a separately configured Connect service and matching DNS/TLS and sign-in settings; pointing a DNS record at a laptop does not complete that setup.

2. Pair the second laptop#

Start on the primary computer, then run the installer on the second laptop.

  1. On the primary, open Settings → Machines → Add a machine.
  2. Copy the generated installer command. If the dialog offers a method choice, choose Installer command.
  3. On the second laptop, open a terminal and paste that command before its displayed expiry. Use the command as generated; it contains the pairing details for your existing instance.
  4. Let the installer finish. It installs the host daemon and configures it to reconnect automatically.
  5. Return to Machines on the primary and wait for the second laptop to show Online. The pairing dialog also reports that the machine connected.

Check: both computers appear under the same instance. Your remote address stays the same. A full desktop app installation on the second laptop is optional if you only want it to execute work.

The installer keeps this connection under ~/.zcc-machines/<instanceId>. An independent Zana installation on the second computer keeps its own data.

3. Add the project's folder on that machine#

Pairing makes the machine available; registering a checkout tells Zana where that project's files live on it.

  1. Clone or copy the repository to the second machine if you have not already done so. Confirm the folder exists there.
  2. In your shared Zana, open the existing project's settings and find Checkouts on your machines.
  3. Under Machine, choose the second laptop.
  4. Under Existing folder, enter the absolute path on that laptop.
  5. Click Add checkout.

Check: the project lists a checkout on each machine, with each machine's own path. It keeps the same project ID and history. For a separate project that lives only on the second machine, use Add project, select that machine, and choose its folder instead.

Checkouts are not synchronized automatically. Use Git or an explicit file transfer to move changes between laptops. Provider credentials and repository instructions come from the machine executing the work.

4. Run work from your remote address#

  1. Open your existing Zana address in a browser and sign in with the same account.
  2. Open New Chat, choose Thread, and select your project.
  3. Select the second laptop in the machine picker before sending your task.
  4. Confirm the selected machine and checkout, then send. The Thread runs on the second laptop and appears in your shared Zana.

For a first check, ask the agent to report its working directory without changing files. It should match the checkout you registered on the second laptop. Ordinary terminals and file/Git operations can also use the selected machine.

An offline machine or missing checkout produces an error. Zana does not silently run the task on another machine. For CLI Agents or Squad/Team launches, choose a project on the primary; secondary-machine execution for those workers is deferred. You can still control primary-machine workers from the browser.

5. Open the same Zana in a second desktop app#

This is optional if you already use the browser.

  1. Install and open Zana on the second laptop.
  2. Open Settings → Remote access and expand Open an existing Zana instead.
  3. Under Open a shared Zana, click Sign in and finish browser sign-in with the same GitHub account.
  4. Find your primary instance and click Open instance. Use Refresh instances if needed.

The desktop now displays that instance's projects, threads, and machines. Opening it preserves the second laptop's independent local projects and running work. To execute work on this laptop as part of the shared instance, complete steps 2 and 3 as well.

Troubleshooting#

What you see What to check
Your remote address shows offline Keep the primary awake, Zana running, and Remote access enabled with status Connected.
The second machine is offline Wake it, check its internet connection, and use Fix if available. Use Pair again to repair Connect pairing while keeping its host ID.
The installer code expired Generate a new code in Add a machine and run the new command on the second laptop.
A Thread cannot find its checkout Confirm the folder exists on the selected machine and add its absolute path under Checkouts on your machines.
The provider cannot start or needs sign-in Install and authenticate that provider on the execution machine; a login on the primary is not copied there.
The browser shows different projects Verify the account and address. Separately connecting two independent instances creates separate records.
Changes from one laptop are missing on the other Commit and push/pull with Git, or transfer the files explicitly. Checkouts do not sync automatically.
A CLI Agent or Team launch is unavailable on the second machine Select the primary machine for those launches; use a Thread for secondary-machine execution.

Shared projects and data#

Checkouts are not synchronized automatically. Git or another explicit file transfer workflow moves code between them. Repository instructions and provider credentials belong to the execution machine; project metadata has one canonical owner. Library remains at the original project location, so that host must be reachable when it owns the metadata.

Library files and folders can be moved between projects or between a project and the global Library. Both owners must be reachable. Zana verifies the destination before removing the source and preserves document identities and binary files. An interrupted transfer resumes when Library refreshes; conflicting external edits preserve the files and report a recovery problem. A transfer is limited to 1,000 entries and 64 MiB.

If a Goal or Schedule shows Check worker, that action checks the instance's session inventory and durable launch records. It can reattach a known worker or resolve a confirmed exit or a launch that never started. It leaves work paused and never starts a replacement. Missing or ambiguous evidence remains blocked.

Installed plugins use the shared instance. Older plugins that require the native module bridge are listed as Owner desktop in shared clients; use or manage them on that computer. New plugin installation also remains on the owner's desktop. Supported installed plugins remain available through the shared client.

Use Pair again to repair an enrolled Connect machine while retaining its host ID. Removing one machine revokes its execution access. Removing an extra checkout keeps the shared project, history and files; active work must stop first. Creating another independent instance in the account dashboard is a separate operation with separate records.

Other ways to connect a machine#

The existing compatibility paths remain available:

  • Enrolled machines run a host daemon. The other box outbound-connects to this app. Add a folder on that machine from Settings → Machines (or the host picker when adding a local project). Threads then execute there.
  • SSH remotes are a project folder on a host from ~/.ssh/config. Add remote registers the project only. Install the host daemon from composer Install (or Settings → Machines). Composer Send waits until the daemon is bound and online. The env chip shows user@host · path · Online. This machine's host daemon must be connected (it owns ~/.ssh for the install).

Copy-paste join remains for boxes you cannot SSH to from this machine.

Connect execution-machine credentials are distinct from browser/phone access. Browser and phone credentials cannot call host-internal routes. The Connect machine transport carries enrollment, host WebSocket, authorized tool callbacks and installation artifacts through a closed route inventory. See implementation contracts for the boundary.


Legacy machine relay reachability#

The product server still binds loopback (127.0.0.1). Another computer cannot enroll against http://127.0.0.1:<port>. Official desktop builds carry the public Heroku origin and relay token (inlined at electron-vite build from ZCC_APP_URL and ZCC_RELAY_TOKEN). This laptop dials wss://<origin>/_zcc/relay — Heroku never inbound-connects to the laptop.

Precedence: runtime env, then the values baked into that build, then Settings → Machines → Public app URL, then the repo public-app-url file. pnpm dev seeds ZCC_APP_URL from that file when the env is unset. Do not commit the token; set the same ZCC_RELAY_TOKEN on Heroku and in the release/CI environment (GitHub secrets ZCC_APP_URL / ZCC_RELAY_TOKEN, or put them in gitignored .env for local pnpm dev). The public dual-arch build is produced by pushing a vx.y.z tag.

The token authenticates a laptop to open a session. Isolation between laptops is the session URL (/t/<sessionId>), not a personal key. Join/enroll through that id stays open while this laptop’s relay is connected. The join hint renews in the background so Install / Fix never hit a closed window. Host websockets keep working until this app quits. If the laptop tunnel drops, join/enroll return relay_offline until Zana reconnects.

Join commands use https://<origin>/t/<sessionId> so remotes route to the right laptop.

If the origin is baked/set but the tunnel is down, Install fails with relay_offline (keep Zana running). Dev builds without those env vars stay loopback; use SSH reverse-tunnel pairing.

That origin is used in the join command and as the Host-header allowlist on the laptop (enroll, host websocket, /install.sh).

The Heroku dyno has a separate path allowlist (website/relay/allowlist.json, mirrored in the product server): /install.sh, /install/version, /install/zcc-host.tgz, enroll, interactive-request (and interrupt), and /internal/hosts/ws. Other product HTTP — including /internal/hosts/tool-call — is not relayed.

Operator detail for the front door (ZCC_RELAY_TOKEN, node relay/front-door.mjs) is in `website/README.md`.

Keep the product server on loopback and use the authenticated pairing relay. Exposing its port directly would put an unauthenticated control plane on a network.

The machine-pairing URL serves enrollment and host traffic. Use Zana Connect through Remote access for authenticated browser and phone access at your personal address. SSH reverse-tunnel copy-paste (below) remains the offline fallback for machine enrollment.


Legacy installer and SSH enrollment#

  1. Open Settings → Machines and choose Add a machine.
  2. Copy the one-line installer. It looks like:
curl -fL ${publicAppUrl}/install.sh | sh -s -- \
  --join-code <zcde_...> --host-id <id> --server ${publicAppUrl}
  1. Run it on the computer that should execute work. The join code expires in 15 minutes and can be redeemed once. The Machines list turns the new row online when the daemon's websocket is open.

If the machine is an SSH host (for example limited-pony) and you have not set a public app URL, Add machine copies a laptop-side command instead:

ssh -o ExitOnForwardFailure=yes -R 18782:127.0.0.1:<zcc-port> limited-pony \
  'curl -fL … http://127.0.0.1:18782/install.sh | sh -s -- --join-code … --host-id … --server http://127.0.0.1:18782'

Paste that in a terminal on this computer. It reverse-tunnels product HTTP to the remote host and runs the installer there. Leave the SSH session open so the daemon can keep that tunnel. The installer looks for Node 22+ on PATH, then nix / nvm / fnm / volta (a Node 20 PATH entry is skipped). Override with ZCC_NODE=/path/to/node.

The installer requires Node.js 22 or newer on the remote box. Manual pairing downloads the host-daemon tarball from /install/zcc-host.tgz. Composer Fix (enrolled machines that are offline) pipes that same tarball over SSH from this machine instead of asking the remote to curl it.

Each legacy joined server gets its own daemon instance and data directory (~/.zcc-machines/<server-host>); Connect uses the durable instance UUID above. Joining never touches a full local install's ~/.zcc. Subsequent runs reuse the reserved local API port under ~/.zcc-machines/host-daemon-ports/; pass --host-daemon-port <port> to override.

On macOS the installer loads a LaunchAgent. On Linux it enables a systemd user unit when that bus is available; Salesforce workspaces and other boxes without user systemd keep the daemon running in the background instead of hanging. Both start the daemon with --auto-update.


Fix from the composer#

When an already-paired machine is offline, the composer shows Fix. If Zana stored an SSH alias for that host, Fix restarts the LaunchAgent or systemd user unit and reinstalls if restart does not reconnect. If no SSH alias is stored, Fix asks you to pick a host from ~/.ssh/config, then retries.

Fix needs a public origin (baked into the official app, ZCC_APP_URL, Settings, or the repo public-app-url file), not loopback. This machine's host daemon must be connected — it owns ~/.ssh and performs the SSH. If SSH cannot run, copy the Settings → Add machine join command.

Add remote project registers the SSH project only. Composer Install enrolls a host daemon over SSH and stays available until a daemon is bound. Send is blocked until that daemon is online. If SSH cannot complete the install, retry from the composer or copy the reverse-tunnel command.


After it connects#

  1. New project — pick the machine in the host picker and browse its disk (or paste a path on that box).
  2. New thread / home composer — pick the machine when more than one host is connected, or when an enrolled machine is offline (Online/Offline in the picker). A project remembers the host it was created on.
  3. Provider CLIs — each machine row lists Codex / Claude (and other) CLI install state. Use Update all when any enrolled box is missing or outdated.
  4. Permission ceiling — Settings → Machines can cap that box at accept-edits or auto. Owner-session only; a thread cannot exceed the ceiling.

Machine names are labels and may be duplicated; the host id is the stable handle. The laptop that runs the product server is the primary machine and cannot be removed from the list.


Local Docker trial#

A Linux box lives in docker/remote-machine. It is the same enroll path as a real remote: Node 22, /home/zcc/workspace, SSH on port 2222, no systemd user bus (the installer nohups).

Start it and leave it idle:

pnpm docker:remote-machine

Then either paste the Settings → Add a machine command inside the box:

docker exec -it zcc-docker bash
# or: ssh -p 2222 [email protected]   (password: zcc)
zcc-join --join-code <zcde_...> --host-id <id> --server <url>

Or mint and enroll in one step (pnpm dev must be running). If the laptop relay is connected, this uses https://<origin>/t/<sessionId>; otherwise it publishes a loopback proxy so Docker can reach 127.0.0.1:

pnpm docker:host-daemon

Force a door with --relay or --local. Prove a Linux box can enroll through the session join URL with pnpm test:docker:pairing (needs Docker). Stop with pnpm docker:remote-machine down.

Settings → Machines should show hostname zcc-docker. Add a project at /home/zcc/workspace (sample app is in the repo under docker/remote-machine/workspace). This is a local pairing trial.


Self-update#

If session open reports a newer server protocol, the daemon downloads the server artifact, updates its private install, then exits so launchd/systemd restarts it. Failed attempts fall back to reconnect with a persisted backoff from 5 seconds to 5 minutes. Retry update in Settings → Machines bypasses the current backoff. A daemon never downgrades itself to an older protocol.

To opt out, remove --auto-update from the LaunchAgent plist or systemd unit, then reload the service.


Where to go next#