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 planand 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:
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-agentbefore you update the app, or relaunch the app afterwards; see the note above.
Linux server (apt)¶
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 carriesKillMode=process. If you did edit it, dpkg asks: take the package's version, or keep yours and addKillMode=processwith a drop-in:
sudo systemctl edit shed-server
# in the editor, add:
# [Service]
# KillMode=process
sudo systemctl daemon-reload
/etc/shed/server.yamlis 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-serveritself 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-serveron every install and upgrade, so a unit you had deliberately disabled comes back enabled (though not started). Runsudo systemctl disable shed-serveragain 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-Ndevice behind, down.shed startreuses it andshed deleteremoves it. - From 0.9.0 on, every restart with a VM still running logs systemd warnings like
Unit process … (firecracker) remains running after unit stoppedandFound left-over process … in control group while starting unit. Ignoring.That isKillMode=processdoing its job: the VM is meant to outlive the server process. - Installing the package replaces
/usr/local/bin/shed-server, so it drops thecap_net_adminfile capability thatshed-server setupsets. The systemd unit runs the server as root, so this changes nothing there. Re-runsudo shed-server setuponly if you runshed-serverby hand as a non-root user. - Downgrading to 0.8.x: stop every running shed first (
shed stop, then checkpgrep -x firecrackeris empty). 0.8.x's unit has noKillMode=processand its binary forwards SIGTERM, so a VM still running when the older package restarts the server is killed. Thensudo apt install --allow-downgrades shed-server=<0.8.x version>(ordpkg -ia 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:
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:
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
extensionsorfullimage (shed createafter 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-sessionfrom roost's release assets), or with roost's ownConnect Hostpalette 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:
- Reinstall the Swift 0.8.1 DMG from the release page.
- 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-sessionfrom a roost release older thanv0.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 stopon that target (roost-sessionitself has no stop command), then reconnect from the desktop and use Update, with its consent step. A host shed bootstrapped has onlyroost-session, notroostctl, so install roost'sroostctlon the target first (from roost's releases), then run it from your machine withssh <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 planand the Remote-Control mode ofshed attachare removed, not rebased. There is no--kind/--prompt/--plan/--permission-mode/--skiponshed attachany more, andshed planno longer exists. Handing a plan to a shed is nowshed attach <shed>to get a tab, then the agent inside it.shed attachandshed sessionsare roost-native, with tmux as the unchanged floor. With a local roost app running,shed attachopens a shed as a roost tab; with no local roost app,--tmux, orSHED_ATTACH=tmux, both commands behave exactly as they always have. Seeshed attachandshed sessions.shed-ext-rcno longer ships in newly builtextensions/fullrootfs images — in its place both stages now installstrix(a TUI for staging and reviewing diffs) andprox(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-inertshed-ext-rcbinary — 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-rcdirectly (it was never migrated to roost, unlike the Tauri app's Agents pane); against a shed with noshed-ext-rcbinary 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,gxandgrokkinds. The launch dialog and theshed roost-providerpalette offerclaudeandopencode; 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:
~/.local/bin/crazecrazeon the PATH/opt/homebrew/bin/craze,/usr/local/bin/craze,/home/linuxbrew/.linuxbrew/bin/craze,/usr/bin/craze~/.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:
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:
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 Goshed-machine-rcbinary and its release component) shipped through v0.8.2 and is retired now; seeshed-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 startthem 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-sessionshows 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.20is 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/binfirst) — see craze is shed's agent lane.