--- type: constraint title: Updating the plugin does not update the daemon, and nothing said so tags: [omarchy, install, versioning] verified: - by: armed a block on a machine whose plugin files were three commits ahead of /usr/local/lib/omarchy-selfcontrol, then hit a refusal the panel said could not happen at: 2026-08-29 --- # What happened `omarchy plugin add` updated `~/.config/omarchy/plugins/` on its own. The privileged half under `/usr/local/lib/omarchy-selfcontrol/` stayed three commits behind, because installing it means pressing Install and `setup` deliberately refuses to replace a running daemon while a block is armed. So the panel drew an edit the daemon of that vintage did not accept, and the person got a refusal that contradicted the panel's own caption with no explanation anywhere. Nothing in either half knew the other was a different version. This recurs on every update, because the natural order is to update the plugin and then use it, and the install step is easy to skip. # Why the two halves cannot share a version An installed daemon has no plugin directory to read. `/usr/local/lib` holds `daemon`, the two entry points and `lib/`, and nothing else, so a daemon that derived its version from `manifest.json` would be reading a file that may not exist and, if it does, belongs to a different version of itself. So each half carries its own: `version` in `manifest.json`, `DAEMON_VERSION` in `daemon/daemon`. They are bumped together and `tests/run` fails when they disagree, because a release that bumps one and not the other tells every up to date machine its update is not installed. # Which direction of skew each mechanism survives `daemon_version` is a new field in `status.json` and the schema stayed at 1. That is the whole trick, and it is not interchangeable with a schema bump: - A panel newer than the daemon sees the field missing and reads that as an older daemon. That is the common case on the first update that ships this, where the running daemon predates the field entirely. - A panel older than the daemon ignores a key it has never heard of and keeps working. `parseStatus` refuses a `schema_version` above the one it knows, and draws the daemon as absent when it does. So bumping the schema to introduce the field would have made every older panel refuse a newer daemon outright, which is the one direction of skew a panel cannot report because it can no longer read the line that would tell it. # What this does not catch Version skew, not file skew. Editing the plugin files without bumping `manifest.json` and `DAEMON_VERSION` leaves the two halves reporting the same version while running different code, which is the normal state during development and is not what this is for. # Pressing Install while a block runs `setup` replaces the files and, while `block.state` says armed, does not touch the running service. New code is then on disk and the old process is still enforcing, until somebody runs `setup` again after the block ends: expiry happens inside the old process and does not restart it. The panel therefore offers no install control while armed, because the press would cost a password dialog and change nothing anybody can see. It says the daemon cannot be replaced during a block instead, and offers the install again once the block has ended. A person running `setup` by hand while armed is told the same thing on stdout.