Skip to content

Upgrade Guide: v0.8.2 to v0.9.0

v0.9.0 promotes the Tauri app to the default macOS client, re-pins shed's roost-session protocol to 6, makes craze the desktop's agent lane, and removes the remote-control (RC) stack. The server's RC routes are gone, so this is not a pure client-side upgrade: the CLI/server wire contract loses those routes. Running mixed versions works like this:

  • A 0.9.0 CLI against a 0.8.x server loses nothing the server still has; it simply never calls the removed RC routes.
  • A 0.8.x CLI's shed plan and RC attach modes find no routes on a 0.9.0 server.
  • The phone against a 0.8.x server shows "this server predates 0.9.0" for the features that need 0.9.0.

On Linux, the upgrade stops every running Firecracker shed once. Read Before you upgrade (Firecracker) first.

The macOS update is an in-place client swap, not a config change

If you run the Swift menu-bar app on macOS, the next Sparkle check offers the Tauri build, not a new Swift version — same bundle identity, same signing key, same feed. It should apply as an ordinary update, but your preferences do not carry over, rolling back is not as clean as reinstalling the old DMG, and v0.9.0 is the first release to exercise this path at all. Read "The macOS Tauri update" below before you click Install.

Before you upgrade (Firecracker)

This upgrade stops every running Firecracker shed, once

Upgrading a Linux host from 0.8.x to 0.9.0 stops all of its running Firecracker sheds. Nothing is lost on disk, but anything running in them ends. The one exception: if /etc/shed/server.yaml fails the new binary's config-validate, the post-install step skips the restart, so the old server and its running sheds keep going until you fix the config and sudo systemctl restart shed-server. That restart then stops them, once.

Why: the package's post-install step runs systemctl daemon-reload and then a try-restart of shed-server. The old 0.8.x binary is the one that receives the stop signal, and it forwards SIGTERM to every VMM it started. Only 0.9.0 and later turn that forwarding off (ForwardSignals: []) and ship KillMode=process in the unit, so the next restart or upgrade, and every one after it, leaves the VMs running. This first one cannot.

This applies equally to unattended or non-interactive upgrades (unattended-upgrades, a scripted apt-get -y): the post-install restarts the server whether or not anyone is at a prompt. If a Firecracker host upgrades itself, expect its sheds to stop.

To control it, stop the sheds yourself first and start them afterwards:

shed stop <name>        # for each running shed, before the upgrade
# ... upgrade the host (see below) ...
shed start <name>       # afterwards

or just run shed start <name> for each shed that was running once the upgrade finishes. macOS (VZ) hosts are affected too, under brew services restart: VZ sheds stop on every brew-driven restart until 0.9.1 (#409). See the v0.9.0 to v0.9.1 guide.

Upgrade steps, platform by platform

Do these on each machine that applies. The order that matters: upgrade every shed-server and shed-host-agent before you rely on the new desktop, and see Upgrade order if any server uses auth.mode: mtls.

macOS (Homebrew)

brew update && brew upgrade shed shed-host-agent
brew services restart shed
brew services restart shed-host-agent
shed-host-agent version     # confirm 0.9.0 before you rely on the new desktop

Removing shed-host-agent? Do it before the desktop update

The Tauri app (0.9.0) brokers credentials in-process, so a desktop Mac no longer needs the shed-host-agent daemon. Keeping it is fine; upgrade it as above. If you are going to remove it, do that before you apply the desktop update:

brew services stop shed-host-agent && brew uninstall shed-host-agent

If you already updated the app, quit and relaunch it after removing the daemon. In the default Automatic mode the app picks its broker once at launch and does not fall back if the daemon disappears while it runs, so every shed it brokers loses ssh-agent and credential forwarding until the app is relaunched (#411).

macOS desktop

Open the app and choose Check for Updates… from its menu. Automatic checks are off, so the update is not offered until you ask. The swap from the Swift app to the Tauri build is new in this release; read The macOS Tauri update before you click Install.

  • Login Items. Open System Settings, General, Login Items. The Swift app registered a login item through the system's SMAppService; the Tauri app's launch-at-login toggle uses a LaunchAgent instead. The old Swift entry may remain, so remove it there if you see two, then set launch-at-login again in the new app's Preferences.
  • Preferences do not migrate (see below), and nothing is stored in the keychain, so there is no keychain migration to do.
  • Order matters if you are removing the daemon. Remove shed-host-agent before you update the app, or relaunch the app afterwards; see the note above.

Linux server (apt)

sudo apt update
sudo apt install --only-upgrade shed-server

What dpkg may ask:

  • The unit file (/etc/systemd/system/shed-server.service) is a dpkg conffile. If you never edited it, dpkg replaces it silently with the 0.9.0 one, which carries KillMode=process. If you did edit it, dpkg asks: take the package's version, or keep yours and add KillMode=process with a drop-in:
sudo systemctl edit shed-server
# in the editor, add:
#   [Service]
#   KillMode=process
sudo systemctl daemon-reload
  • /etc/shed/server.yaml is not prompted this time: the packaged template has not changed since 0.8.2, and your file is kept as it is.
  • The restart. The package restarts shed-server itself if it was running (and leaves it stopped if it was stopped). That restart is the one that stops Firecracker sheds, once (see above).
  • The unit is re-enabled. The post-install step also runs systemctl enable shed-server on every install and upgrade, so a unit you had deliberately disabled comes back enabled (though not started). Run sudo systemctl disable shed-server again afterwards if you want it off.
  • What else you may see (all harmless, seen in the upgrade rehearsal on a Firecracker host):
  • The one-time stop leaves each stopped shed's shed-tap-N device behind, down. shed start reuses it and shed delete removes it.
  • From 0.9.0 on, every restart with a VM still running logs systemd warnings like Unit process … (firecracker) remains running after unit stopped and Found left-over process … in control group while starting unit. Ignoring. That is KillMode=process doing its job: the VM is meant to outlive the server process.
  • Installing the package replaces /usr/local/bin/shed-server, so it drops the cap_net_admin file capability that shed-server setup sets. The systemd unit runs the server as root, so this changes nothing there. Re-run sudo shed-server setup only if you run shed-server by hand as a non-root user.
  • Downgrading to 0.8.x: stop every running shed first (shed stop, then check pgrep -x firecracker is empty). 0.8.x's unit has no KillMode=process and its binary forwards SIGTERM, so a VM still running when the older package restarts the server is killed. Then sudo apt install --allow-downgrades shed-server=<0.8.x version> (or dpkg -i a cached 0.8.x .deb).

Linux desktop

The Tauri desktop ships as the shed-desktop package from the same apt repository as the server. If the repository is not set up on this machine yet, add it as in the Linux quickstart, then:

sudo apt update
sudo apt install shed-desktop

If you ran a standalone shed-host-agent daemon on this machine (it is Homebrew-only on Linux, so under Linuxbrew brew services), stop and remove it before you install shed-desktop, because the app embeds the broker:

brew services stop shed-host-agent && brew uninstall shed-host-agent

If the app is already running, quit and relaunch it after removing the daemon. In Automatic mode the app does not fall back to its own broker if the daemon disappears while it runs (#411).

Images

New sheds use 0.9.0's images automatically when server.yaml leaves default_image and image_aliases unset. A pinned ref keeps the old images: change the pin to a v0.9.0 ref to move. To pull the new images ahead of time, on the server host, name the config the server actually runs with (omitting --variant pulls every variant):

# macOS (Homebrew), as your user
shed-server --config "$(brew --prefix)/etc/shed/server.yaml" pull-images

# Linux (apt)
sudo shed-server --config /etc/shed/server.yaml pull-images

craze, on every host you drive

The desktop's craze lane needs craze 0.1.0 or later on each host. Check and upgrade:

craze --version
type -a craze                       # every craze on the PATH, in order
brew upgrade charliek/tap/craze     # macOS or Linuxbrew
sudo apt install craze              # Debian or Ubuntu

or install craze's .deb from its releases page. Check type -a craze after upgrading: shed tries ~/.local/bin/craze first, then a PATH lookup, then the fixed directories. So a stale ~/.local/bin/craze always wins (and type -a does not show it unless that directory is on your PATH, so check it directly: ls -l ~/.local/bin/craze). After that, a stale /usr/local/bin/craze wins only when it is earlier on the PATH than the packaged one, or when the PATH lookup finds no craze at all. The PATH shed uses to reach a remote host is the one a non-interactive SSH command gets, which can differ from your interactive shell's, so run type -a craze over ssh (ssh <host> 'type -a craze') for a remote host. See craze is shed's agent lane for the full ladder.

roost 0.0.21 and roostctl

On the machine you run shed attach from, install roost 0.0.21 and its roostctl; 0.9.0 speaks roost-session protocol 6. A host the desktop bootstrapped earlier needs its old daemon stopped before it can be updated (see roost-session protocol 6).

Existing sheds

A shed built from a 0.8.x image has no roost-session and no craze. Either:

  • recreate it from a 0.9.0 extensions or full image (shed create after the images above), or
  • keep it and put roost on it with the Install button on its roost line in the desktop's Sheds pane (it installs roost-session from roost's release assets), or with roost's own Connect Host palette row, and place a craze binary in its ~/.local/bin.

Its inert shed-ext-rc stays (see below).

Dev servers

If you run a parallel dev server from the source tree, rebuild and restart it from the new tree:

make build && make dev-server-restart        # macOS dev server
make build && make dev-server-restart-fc     # Firecracker dev server

The Firecracker dev server runs under sudo nohup, not systemd, so it has no KillMode at all: its first restart onto 0.9.0 stops its VMs once as well.

The phone

Use the shed-mobile release built against 0.9.0. Until it is out, the current phone app works against a 0.9.0 server.

What changed

The macOS Tauri update

Every roost feature shipped since the roost integration began — the machines tab, the roost bootstrap, agent lanes (craze and opencode) — exists only in the Tauri client. The Swift app never gained any of it, yet Swift was what every stable tag shipped on macOS. v0.9.0 fixes that: the release workflow's mac job is now the Tauri build on every tag, stable or prerelease. The Swift release job is retired; Swift's sources stay in the tree for now (demolition is filed as #364).

The Tauri macOS bundle carries the same bundle identifier (ai.stridelabs.ShedDesktop), the same Sparkle SUPublicEDKey, and the same SUFeedURL as the Swift app. Those are the conditions Sparkle needs to treat it as an ordinary update to the app you already have, so a Mac running Swift 0.8.x should receive it in place — no separate download, no manual install.

v0.9.0 is the first release to actually run this path

The mac release job that builds the Tauri DMG has never run before this tag, and the two-release rehearsal that would normally prove an in-place Sparkle update was deliberately skipped for 0.9.0. The identity and key are verified to match, which is what makes the update possible — it is not the same as having watched it succeed.

If Sparkle reports a failed update, nothing is broken: your Swift app keeps running. Download ShedDesktop-0.9.0.dmg from the releases page and install it by hand, and please open an issue — that is a bug worth fixing in 0.9.1.

Preferences do not migrate. The Swift app stores its preferences in UserDefaults (desktop/Sources/ShedDesktopApp/PreferencesStore.swift); the Tauri app reads prefs.json (desktop/tauri/src-tauri/src/prefs.rs). There is no migration between the two stores, so after the update your terminal, approvals, policy, and launch-at-login settings all start from Tauri's defaults, not whatever you had configured in Swift. Re-apply anything you had customized.

The honest rollback

If you want to go back to the Swift app, reinstalling the Swift 0.8.1 DMG from the release page brings Swift back — but it does not stop the appcast from offering 0.9.0 again. The reinstalled Swift binary reads the exact same feed (SUFeedURL) with the exact same key (SUPublicEDKey) that just offered you the Tauri update, so the next Sparkle check re-offers 0.9.0. This is not a bug to be fixed later in this release — it is the direct consequence of the two apps sharing one update identity, and the recipe is:

  1. Reinstall the Swift 0.8.1 DMG from the release page.
  2. When Sparkle offers 0.9.0 again, choose "Skip This Version" — or accept the recurring prompt if you plan to move back to Tauri eventually anyway.

One thing rollback does restore cleanly: your Swift preferences. They were never touched or removed by the Tauri update (different store, different file), so reinstalling Swift 0.8.1 picks its UserDefaults back up exactly where you left them before the update.

roost-session protocol 6 on every bootstrapped host

If the desktop app previously bootstrapped a roost-session daemon onto a shed or a machines: entry for you (see Putting roost-session on a shed or machine), that host is running an older roost-session generation and needs to be updated:

  • A host running protocol 5 (or the original protocol 4 from an early bootstrap) is refused by name: the client tells you exactly which host and which protocol, as a report with no button. A running old daemon is never stopped or replaced by shed, so you stop it yourself, at your own pace (below); only then does the stopped, incompatible binary get an Update button, which goes through the usual consent step.
  • If you have a real, independently-installed roost-session from a roost release older than v0.0.20, it also speaks an old protocol — v0.0.19, the last release before this one, speaks protocol 2, well behind shed's protocol 6. It is refused loudly the same way.
  • To recover such a host: stop its session with roostctl session stop on that target (roost-session itself has no stop command), then reconnect from the desktop and use Update, with its consent step. A host shed bootstrapped has only roost-session, not roostctl, so install roost's roostctl on the target first (from roost's releases), then run it from your machine with ssh <target> roostctl session stop.

The phone-install rung is fixed by this release. roost v0.0.20 is the first published release that speaks session protocol 6, and v0.9.0 pins v0.0.21, roost's bug-fix release after it: the release-asset install path now downloads and checksum-verifies a roost-session onto a fresh Linux target on any architecture roost publishes a build for, with no $ROOST_SESSION_INSTALL_BIN and no sibling binary needed. Those two rungs still work and still take precedence, in that order.

The RC hub is retired end to end

v0.9.0 finishes the retirement of RC: the whole remote-control-hub stack — the guest binary, the server's RC routes, the CLI's RC surface, and the Rust engine — is gone.

  • shed plan and the Remote-Control mode of shed attach are removed, not rebased. There is no --kind/--prompt/--plan/--permission-mode/--skip on shed attach any more, and shed plan no longer exists. Handing a plan to a shed is now shed attach <shed> to get a tab, then the agent inside it.
  • shed attach and shed sessions are roost-native, with tmux as the unchanged floor. With a local roost app running, shed attach opens a shed as a roost tab; with no local roost app, --tmux, or SHED_ATTACH=tmux, both commands behave exactly as they always have. See shed attach and shed sessions.
  • shed-ext-rc no longer ships in newly built extensions/full rootfs images — in its place both stages now install strix (a TUI for staging and reviewing diffs) and prox (a process manager with an HTTP API and TUI) from the stridelabs apt repo. A shed created from an image built before this change keeps its existing, now-inert shed-ext-rc binary — nothing on the current CLI, server, or desktop app calls it any more, and it is not removed or replaced by an upgrade; it simply sits unused until the shed is recreated from a newer image.
  • The Swift macOS desktop app's Agents pane stops working against a v0.9.0+ shed image. The Swift app's Agents pane still SSHes into the shed and invokes shed-ext-rc directly (it was never migrated to roost, unlike the Tauri app's Agents pane); against a shed with no shed-ext-rc binary it reports "shed-ext-rc is not installed on this shed — update the shed image." This only matters if you rolled back to the Swift app (see The honest rollback above) — the Tauri app, the default macOS client since this release, reads roost directly and is unaffected.

None of this requires action on upgrade beyond recreating a shed from a current image whenever it's convenient — an old shed keeps working exactly as it did, just without a callable shed-ext-rc.

craze is shed's agent lane

craze is now the lane for every agent shed runs other than Claude and opencode: one hub per machine in front of its cursor, grok, gx and native sessions. In the Tauri app each host's craze sessions are rows of their own, with a live transcript and approvals — plus Stop where the session can be stopped (not one hosted by a craze TUI), and a settings sheet where it has a model, mode or option to change — and New craze session starts one. See Agent lanes § craze.

What it replaces. shed's own per-agent integrations are gone:

  • Retired: shed's codex, cursor, gx and grok kinds. The launch dialog and the shed roost-provider palette offer claude and opencode; every other agent starts through craze, or as its own TUI in a tab (below).
  • The gx lane — a transcript for gx sessions — never shipped. craze replaces it.
  • The claude, codex and cursor RC lanes are gone as well (see Retired in 0.9.0 below).

What a codex tab looks like now. A plain row: its kind as roost reports it (codex), roost's activity and working directory, and End session — no Transcript, no typed-input prompt. codex is not a craze provider, so this is how it stays. To start an agent's own TUI with flags of your choosing, use the launch dialog's Run a command mode (for example codex --model gpt-5); see Run a command in a tab.

Installing craze on a machine. shed needs craze 0.1.0 or later on each host whose craze sessions you want to see. A host without craze simply shows none; a machine with craze 0.0.1 says "craze on this machine is too old for shed; update it" on its Machines-pane card. shed finds craze with craze's own published ladder, in this order:

  1. ~/.local/bin/craze
  2. craze on the PATH
  3. /opt/homebrew/bin/craze, /usr/local/bin/craze, /home/linuxbrew/.linuxbrew/bin/craze, /usr/bin/craze
  4. ~/.nix-profile/bin/craze, /etc/profiles/per-user/$USER/bin/craze, /run/current-system/sw/bin/craze

craze's Homebrew formula, its apt package and its .deb all land on a rung (see craze's own install guide, docs/getting-started/quick-start.md in the craze repository). ~/.local/bin comes first, so a craze placed there wins over a packaged one — useful for a hand-placed build, and a trap if it is stale. A go install build lands in ~/go/bin, which the ladder does not search; link it in:

mkdir -p ~/.local/bin
ln -sf ~/go/bin/craze ~/.local/bin/craze

The PATH, and [agents]. The hub hands its environment to every session it starts, so shed runs craze with the ladder's own directories ahead of the original PATH: an agent in ~/.local/bin, Homebrew, /usr/local/bin, /usr/bin or a Nix profile is found even over a bare SSH exec. An agent binary anywhere else needs an absolute path in ~/.craze/config.toml:

[agents]
cursor = "/opt/cursor/bin/cursor-agent"

A hub keeps the environment of whatever started it: one started earlier by a craze TUI or by craze new over SSH keeps its PATH until it exits (60 seconds after its last session and client have gone). Shell exports such as a provider's API key do not reach a hub shed started; use craze auth login.

macOS and cursor. cursor needs the Mac's GUI login session, and a hub first started over SSH cannot start it (craze SF-126). The desktop on the Mac joins the hub already running there or, when none is running yet, starts one in the GUI session, where cursor works; a hub first started over SSH stays SSH-born until it is stopped. Configuring a Mac as a machines: entry starts nothing on it — remote hosts are attach-only — but opening the create sheet for that Mac from another machine starts an SSH-born hub there if none is running, and cursor is then dimmed with craze's reason and fix: kill <hub pid> (no session ends), then craze ps in a terminal on the Mac.

Firecracker VMs now survive a shed-server restart

On Firecracker, stopping or restarting shed-server used to take every running shed's VM down with it (#372) — a plain kill -TERM of the server leaves VZ VMs running, but a launchd restart (brew services restart shed) does not until 0.9.1 (#409; see the v0.9.0 to v0.9.1 guide). Two things caused it: the firecracker-go-sdk relays SIGINT/SIGQUIT/SIGTERM/SIGHUP/SIGABRT to each VMM child by default, and systemd's default KillMode=control-group reaped anything that survived the relay. v0.9.0 turns the relay off and ships KillMode=process in the unit, so from 0.9.0 on systemctl restart shed-server — including the restart every apt upgrade performs — and an outright crash both leave the VMs running. The upgrade from 0.8.x itself is the exception: the restart it performs signals the old binary, which still relays (see Before you upgrade). After that, when the server comes back, the startup resume walk added for #315 re-attaches each one and shed list / shed exec carry on. The operational consequence, stated plainly: the VMs now outlive the server that manages them. If shed-server cannot come back — bad config, failed upgrade, crash loop — the running sheds stay alive and unmanaged until it does, holding their memory, TAP devices and vsock sockets, with no shed command able to reach them; fix the server and they are managed again, or kill the firecracker processes by hand if you need the host resources back first. Because a surviving VMM is now the normal case, shed stop and shed delete after a restart verify that the recorded pid is this shed's VMM — by the --api-sock path in its command line — before signalling it, so a pid the OS recycled onto another shed's VM can never be killed by the wrong shed's command. One consequence of that identity check: do not change firecracker.socket_dir while sheds are running — a surviving VMM carries the old path in its command line, so after a server restart the running sheds would read as stopped and be abandoned. Stop every shed first, then change it. The egress helper is unaffected: it sets its own parent-death signal (internal/egress/pdeathsig_linux.go) and still exits with the server. If you have locally edited /etc/systemd/system/shed-server.service, dpkg will prompt on upgrade — keep your version and add KillMode=process under [Service] yourself (a systemctl edit drop-in works), then systemctl daemon-reload.

Retired in 0.9.0

These were retired in the 0.9.0 window, and machine-rc was still shipping in 0.8.2:

  • machine-rc (the Go shed-machine-rc binary and its release component) shipped through v0.8.2 and is retired now; see shed-machine-rc (retired). Nothing in 0.9.0 calls it, and its 0.8.2 brew and apt packages stay frozen at 0.8.2, unsupported. If you installed it, remove it:
brew uninstall shed-machine-rc     # macOS / Linuxbrew
sudo apt remove shed-machine-rc    # Debian / Ubuntu

The machine-side RC hub it ran no longer exists anywhere; a native machine's agent sessions are roost tabs, the same as a shed's. - sx was sunset before it was ever released; see sx (sunset). No sx artifact was published, so there is nothing to withdraw. - The claude, codex and cursor RC lanes (transcript tail, JSONL tail, hook-ingest) are gone in favor of roost-derived status; see The RC helper. A shed row's state is liveness-only for those kinds, and activity comes from roost once a host has a roost-session rather than from a second, shed-side derivation.

Beyond uninstalling machine-rc, none of this needs action on upgrade; it is context for anyone whose muscle memory still reaches for sx or the old RC lane behavior.

Pre-upgrade checklist

  • Desktop users removing shed-host-agent: remove it before applying the desktop update, or relaunch the app afterwards (#411).
  • Linux Firecracker hosts: this upgrade stops every running shed once; stop them first or shed start them afterwards, and note that unattended upgrades do it too. See Before you upgrade (Firecracker).
  • macOS desktop users: decide before you click Install whether you're ready to lose your Swift preferences. If not, note your current terminal/approvals/policy/launch-at-login settings so you can re-apply them in Tauri after the update.
  • Anyone who used the desktop's roost-bootstrap feature: a running old roost-session shows as a report with no button; stop it (see the section above), after which you update each host explicitly through the Update button and its consent step.
  • Linux/phone roost-session installs: no action needed — the release-asset install rung starts working with this release, which pins roost v0.0.21 (v0.0.20 is the first release speaking protocol 6).
  • Server restarts no longer break running sheds' credential channel (#315). Older servers: shed stop <name> && shed start <name>.
  • craze sessions in the desktop: install craze 0.1.0 or later on each host where you want them, somewhere on its ladder (~/.local/bin first) — see craze is shed's agent lane.