With the venv active, a gateway started by hand is `python -m hermes_cli.main gateway run`: the interpreter is bare, the executable resolves outside the runtime, and no runtime path is on the command line, so the removal took it for a stranger and refused over the files it holds rather than closing it. Hermes's own package named with -m is the program.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Remove > AI refused whenever anything had Hermes's files open and told the user to close it and try again, and the thing open was Hermes: the agent in the terminal that choosing it as the default agent leaves running, the desktop app, a gateway. Those are the removal's to close. It now stops the gateway unit that upstream's `hermes gateway install` wrote, since that unit starts the runtime about to be deleted and would start it again the moment it was killed, then ends every process whose program lives in that runtime or in the package, and only then refuses over whatever is left, which is somebody else's: an editor on a skill, a shell sitting in ~/.hermes, a writer on the state database. Both are judged by the program, never by a later argument or by the home served, so an editor opened on a runtime file is the user's and a unit running a Hermes kept elsewhere is left alone whatever home it serves; for an interpreter the program is the script it runs, so a gateway started by hand as `python .../hermes gateway run` is found too. The refusal comes before anything is touched, so a run that stops leaves Hermes running as it was. A unit that will not stop still aborts the removal, as dropping the package would strand a live gateway on deleted code.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The refusals that leave a self-installed Hermes alone came before the command was replaced only for a finished runtime. An unfinished one was still bootstrapped over a git status that could not be read, since a failed status read as a clean tree, and a half-built app beside it, or an edit the Linux runtime patch could not land on, was found only after the user's command had been saved aside and replaced and main switched. All three are asked first now, in both states, so a run that is going to stop leaves the launcher, the checkout and ~/.local/bin as they were.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hermes is only ever installed through the app. Choosing it as the default agent used to stand aside for a hermes that worked but came from somewhere else, and to refuse one that predated seeded sessions; both left the machine on a Hermes that was not the app's, which is the one Omarchy prepares for in-app updates and hands the theme to. Now --check answers only for the app's own Hermes, and --now installs the app whatever answered to hermes before, saving the previous command aside as it always did for the app path. The desktop installer no longer adds the package itself, since the shared install does.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Whether the environment mise built is gone was read by searching the global listing for the prefix `"pipx:hermes-agent`, so a tool that merely starts the same way, `pipx:hermes-agent-tools` say, read as the retired one still requested. Removing the real one changed nothing, the recheck failed again, and the migration stayed pending on every update. The key is matched whole now, with or without its options.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The wait before clearing a stale shallow.lock looked for a git whose command line began with `git`, so one started as `/usr/bin/git`, which is what a caller that resolved it with `which` runs, was never seen: its lock, once a minute old, would have been cleared under it and a second fetch started against the same shallow file. The path is allowed only ahead of the name, at the start of the command line, so a process that merely names git in an argument, an editor opened on /usr/bin/git from inside the runtime say, is not waited for.
Co-Authored-By: Codex XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Upstream's installer runs `npx playwright install chromium` for the browser tools, and npx asks before fetching a package it does not have. Over ssh, with no terminal, it goes ahead; in the floating terminal the menu opens for choosing Hermes it printed "Ok to proceed? (y)" and waited, so an install a user had every reason to walk away from sat there until someone typed y. The mise-built Hermes this replaces never asked anything. `--skip-setup` already answers the wizard the same way, and the app's own bootstrap never had a terminal to ask in.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Choosing Hermes as the default agent built it through mise: a pipx environment with no checkout, so `hermes update` had nothing to move, and the only Hermes that could update itself was the one Hermes Desktop set up. Both paths now run the same setup. omarchy-install-hermes-cli installs the hermes-desktop package and runs upstream's installer from it, pinned to the packaged release and started on main, exactly as Install > AI did; omarchy-install-ai-hermes is that plus opening the app. The terminal, the default agent and the app share one runtime, and it updates itself.
--check answers whether --now has anything left to do, not merely whether a hermes runs: choosing Hermes from the menu asks first and opens a terminal only on a no, so a yes has to mean no minutes-long step would run where nobody can see it. With the app installed that means the runtime's own command, its completion marker and the seeded packaged app; a finished runtime whose command is gone, somebody else's, or its own but unable to run gets it back from upstream's path stage without bootstrapping again. Either way the command has to be the one PATH finds, because omarchy-agent runs bare `hermes` and Omarchy puts mise's shims ahead of ~/.local/bin; a command in the way is named rather than installed over. The modes are named outright because the app's launcher used to call this command with no arguments to reconcile a mise copy; a default of --now would turn every launch into an install. --check still refuses to run the retired wrapper, since running it built Hermes through mise, and a machine whose migration is pending can still have it on PATH.
Provisioning no longer writes the wrapper, Remove Preinstalls no longer looks for it, and the wrapper, the environment it built and what proves them Omarchy's are known to the installer alone: --retire-mise is the migration's whole job, and --now runs the same removal once the runtime installer has saved the wrapper aside, so a user who chose Hermes before their migration ran is not left with mise's shim answering `hermes`. Only the wrapper proves the environment is Omarchy's, at its path or in that saved copy, so the environment goes first and the wrapper last, judged by mise neither having it installed nor still requesting it; a removal that leaves either behind, or a listing that cannot be read, mise missing included, stops with the commands to finish by hand and leaves the migration pending. The migration that once installed the wrapper is kept as a no-op for late updaters, and one whose default agent was Hermes is told to choose it again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Keep screen-recording state out of world-writable /tmp
* Compare the /tmp name across the run instead of requiring it absent
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Fall back to the state directory when there is no runtime dir
* Let the /tmp snapshot come back empty
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Resolve the region file the same way in the resizer
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Protect recording fallback state and document its path
---------
Co-authored-by: Omabot <omabot@omarchy.org>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repo moved to the omacom org. GitHub redirects the old URLs, but
`omarchy channel set dev` was still cloning from basecamp/omarchy, which
left every dev checkout with a stale origin remote that confuses gh
(pr create fails with "No commits between omacom:quattro and
basecamp:<branch>"). Update the clone URL, the quattro upgrade tarball,
the update-confirm release link, the systemd Documentation link, and
the manual.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LSFKDatumRZHB8zqk5CP5C
omarchy-channel-set runs under set -e, so a failure after it has begun
mutating the system (dev link, pacman channel, packages) died silently
with the switch half-applied. Trap ERR once the mutation phase starts
and say how to pick the switch back up.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The floating-terminal presentation wrapper showed the green "Done!"
prompt for every exit code except Ctrl-C, so a failed update or channel
switch closed looking like a success. Pass the command's exit code
through to omarchy-show-done and render a red "Failed (exit code N)!"
prompt when it is non-zero. The pkg install/remove pickers get the same
treatment.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Upgrading systemd runs its post_upgrade scriptlet mid-transaction, which
reexecs both the system manager and every user manager. When pacman runs
inside a user-session scope (the floating update terminal), that reexec
can SIGKILL it and abandon the transaction halfway, with packages
upgraded but none of the post-transaction hooks run.
Route every Omarchy-owned system mutation through a new hidden
omarchy-update-pacman helper that registers the transaction as a PID 1
scope via systemd-run, keeping it out of the user manager's cgroups.
System scopes survive the system manager's own reexec, and as a bonus the
transaction now also survives its terminal window closing. On unbooted
systems (the installer chroot) the helper runs pacman directly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Electron rewrites ~/.config/Claude for as long as the app runs, so removing the package and deleting the directory while it is open leaves the app running and the directory back within the same second, holding fresh Preferences and crash-reporter state. Driven end to end on an edge worker: remove via the menu with the app open left 11 processes and a recreated ~/.config/Claude; with the app quit first the directory stayed gone. The test stubs pkill so the suite cannot take a developer's own Claude with it, and asserts the remover reached for it.
Co-Authored-By: Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Follows the ChatGPT flow: the Install > AI entry runs
omarchy-install-ai-claude in a floating terminal, which installs the
claude-desktop package (Anthropic's Linux desktop beta, repacked from
their Debian repo in omarchy-pkgs) and opens the app. Remove > AI
drops the package along with ~/.config/Claude and ~/.cache/Claude,
the Electron directories the desktop app owns, while keeping
~/.claude, ~/.claude.json, and ~/.cache/claude-cli-nodejs: those
belong to the Claude Code CLI, which ships in its own package and
survives this removal, just as the ChatGPT remover keeps the Codex
CLI.
The menu mark is a new U+E90E glyph in the Omarchy icon font, from
Simple Icons' Claude mark, so it reaches desktops through the next
omarchy-settings release.
Co-Authored-By: Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Relay the Elgato Cam Link 4K as a 16:9 virtual camera
Browser meeting apps such as Zoom's web client ask the Cam Link for a
standard-definition stream, and Chromium settles on the smallest mode it
offers, 640x480. The Cam Link fills that 4:3 frame by cropping its 16:9
input, and the app then paints the frame into a 16:9 tile, so everyone
comes out stretched wide. The web client has no HD switch to avoid it.
Hide the raw capture node from users and re-expose it through v4l2-relayd
as a 1280x720 virtual camera with the same name, so there is still just
one "Cam Link 4K" to pick and no way to negotiate 4:3 from it. udev
starts the relay whenever the Cam Link enumerates and stops it on unplug,
and the relay only pulls frames while something is watching. The sink
runs unsynced because v4l2src stamps each frame with its capture time,
which a synced sink treats as already late and drops.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Take the review fixes for the Cam Link 4K relay
Tie only the device's stop into the relay instance. A start dependency on
it left a job waiting on a device that never comes whenever the base
v4l2-relayd.service is started without a Cam Link attached, since the
package generator wants every configured instance.
Let the loopback unit rerun on each relay start, so a deleted or unloaded
device is recreated on replug instead of the oneshot staying satisfied.
Start the relay outright at the end of the migration. The udev trigger
only starts it when the rule is new to the device, and a failed module
build would otherwise pass silently with the raw camera already hidden.
Run the hardware fix after the Panther Lake kernel swap, as it pulls in a
DKMS module that would otherwise build twice.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>