Tidying the terminal agent's Hermes away only happened in the menu installer,
so `pacman -S hermes-desktop` on its own, or an install interrupted after the
package landed, left two Hermeses -- and by then the menu entry that would
have noticed is disabled, because we are installed.
Co-Authored-By: Codex XHigh <noreply@openai.com>
The package built from omacom-io/herdr, a fork pinned to a commit and versioned 0.8.0.r13. Its only divergence was three commits replaying an agent's CLI options when a session resumed, which upstream declined twice — from a contributor in herdrdev/herdr#2036 and from us in herdrdev/herdr#2614, closed in favour of an agent resume manifest meant to supersede it. Those three are dropped; every other commit the fork carried is in v0.8.2.
The fork also self-reported "0.8.0" while speaking wire protocol 20, which upstream's published 0.8.0 did not: it spoke 19. An official client attaching to an Omarchy host therefore saw a server claiming to be its own version yet refusing to talk to it, and offered to stop it without ever naming the protocol. That is omacom-io/omarchy-pkgs#161.
Upstream released v0.8.2 today, and it settles both halves. It carries protocol 20, and the stable manifest now publishes 0.8.2 at protocol 20, so an official client and this package agree. It also contains the five features Omarchy contributed after the v0.8.0 tag — configurable outer pane borders, direct pane resize keybindings, move tab keybind actions, centered tab labels and outer terminal window title sync — which is what made packaging the earlier release a regression rather than a return to upstream.
Because the build is now the release it claims to be, it needs no build-identity marking: HERDR_BUILD_CHANNEL and HERDR_BUILD_ID are gone, and the binary reports a bare 0.8.2. The package name is unchanged, so nothing needs a rename, a migration or a database removal to reach existing installs; 0.8.2-1 simply supersedes 0.8.0.r13-1.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.
apply_pkgrel_override compares the checked-in pkgver against the incoming one to decide whether Omarchy's pkgrel metadata has gone stale, but read the two sides differently: previous_pkgver came from get_pkgbuild_field, which strips quotes, while current_pkgver was parsed again in place and kept them. A PKGBUILD writing pkgver='1.0' therefore compared unequal to itself, and the pkgrel metadata of an unchanged package was deleted on every sync.
spotify, rustdesk and limine-mkinitcpio-hook all quote pkgver. None carries pkgrel metadata today, so nothing has been losing a suffix, but rebuild_on writes exactly that metadata and would have had it thrown away on the next sync.
Reading through the same accessor rather than parsing a second time removes the divergence instead of correcting one side of it.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.
Co-Authored-By: Codex XHigh <codex@openai.com>
A review at xhigh found several ways this command could report success while delivering nothing, which is the exact failure it exists to prevent.
A trigger named in rebuild_on but missing from rebuilt_against was never examined, because the comparison walked the record rather than the declared list. Adding a dependency to a package already opted in left that dependency untracked forever. The comparison now walks the declared triggers, so a name the record does not carry reads as changed.
That also retires the separate baseline path. Recording a package's triggers without bumping pkgrel certified a build nobody had checked: a package already broken by a release that moved before it opted in would be recorded as current and never rebuilt. Opting in now costs one rebuild, which is much the cheaper mistake.
A bumped version was only checked against the checked-in one. The floor is what users already have, so a checkout that had fallen behind the repository could be bumped to a version pacman orders below the package it means to replace, with the record advancing regardless. The published database is now the floor, and an unreadable one warns rather than blocks.
Metadata that did not parse dropped its package out of an unscoped run without a word, an unreadable rebuild_on being indistinguishable from an absent one. It is now reported and fails the run.
The workflow reads versions from mirror.omarchy.org, the mirror the x86_64 builder itself uses, rather than whichever mirror the container defaulted to. A mirror running ahead of the builder would record a version the build never linked against, and nothing re-fires once the record matches.
aarch64 stays uncovered and is documented as such: those builds resolve from Arch Linux ARM, one record cannot describe two architectures, and only x86_64 is published today.
bin/sync-rebuilds --self-test covers each of these against a throwaway repository root with pacman and curl stubbed.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.
Co-Authored-By: Codex XHigh <codex@openai.com>
Arch shipped qt6-base 6.11.2-2 on 2026-08-20, and the published 0.3.0.r20.g28771c7-1, built against 6.11.1, stopped starting: undefined symbol _ZN23QUntypedPropertyBindingC1EP23QPropertyBindingPrivate, version Qt_6_PRIVATE_API. quickshell-check.hook caught it post-transaction, but detecting is all it does, so pacman logged the failure and omarchy-update-restart went on to restart a shell whose binary could no longer launch.
The git rev has not moved, so the rebuild only reaches anyone through a pkgrel bump. rebuild_on names the three Qt packages quickshell actually links against: qt6-base for Core, Gui, Widgets, Network, DBus and OpenGL, qt6-declarative for Quick and the Qml libraries, qt6-wayland for WaylandClient. rebuilt_against is seeded with the versions this rebuild will link against, so bin/sync-rebuilds starts from a correct baseline and fires on the next Qt release rather than repeating this one.
🤖 Generated by Opus 5 in Claude Code.
A package that links Qt private API has to be rebuilt whenever qt6-base moves, because Qt_6_PRIVATE_API symbols are not covered by the soname and pacman upgrades Qt out from under the installed binary while the dependency stays unversioned. Nothing here noticed. Both version gates ask whether the package's own source moved, and for a VCS package pinned to a commit that answer stays no through every Qt release.
Unlocking the build gate would not have been enough on its own. A rebuild that reuses the published version string produces a package pacman never offers anyone, so the trigger has to edit git and bump pkgrel, which is why it sits beside sync-aur and sync-upstream rather than inside check-versions or the builder. Once pkgrel moves, both existing gates already do the right thing untouched.
Packages opt in with rebuild_on in .omarchy/package.json. bin/sync-rebuilds records what each was last bumped for in rebuilt_against and compares that to core, extra and multilib, ignoring testing and kde-unstable because those are not what the builder links against. A package with no record yet is only recorded, never bumped: what its published build linked against is not knowable from here, so the first run establishes the baseline. For an AUR-synced package the bump is written as the dotted Omarchy pkgrel suffix in the metadata as well, since the next sync replaces the PKGBUILD wholesale and would otherwise drop it.
🤖 Generated by Opus 5 in Claude Code.
intel-lpmd, pinta and umu-launcher arrived together in the bulk AUR import of 2026-05-07, and all three have since been deleted from the AUR, which is what happens when Arch moves a package into its own repositories. extra now carries intel-lpmd 0.1.0-4 and pinta 3.1.2-2, and multilib carries umu-launcher 1.4.4-1, against the 0.1.0-2, 3.1.2-1 and 1.1.3-1.1 checked in here.
Nothing noticed because bin/sync-aur treats a missing AUR package as a warning rather than an error, so the sync has completed green every run since May while these three sat frozen.
They were never reachable in any case: pacman stops at the first repository carrying a name, and [omarchy] is ordered below core, extra and multilib, so every machine has been resolving the official builds. That also makes the umu-launcher patch here dead code -- it stripped the build down from the AUR's dependency list, but multilib's package is what actually gets installed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fork carried three commits that replayed an agent's CLI options when Herdr resumed its session. Upstream declined that work twice — once from a contributor in #2036 and once from us in #2614, closed in favour of an agent resume manifest system that is meant to supersede it — so the fork was a permanent rebase treadmill for one feature, and it is dropped here.
Packaging the v0.8.0 release instead would have regressed more than the fork gained: configurable outer pane borders, direct pane resize keybindings, move tab keybind actions, centered tab labels and outer terminal window title sync all merged upstream after that tag, so the release predates five features Omarchy contributed. The package follows upstream master and takes the -git name that says so, replacing both herdr and omarchy-herdr.
Master's Cargo.toml reads 0.8.1, a release upstream cut and withdrew hours later, while the wire protocol is already 20 against the published release's 19. An unmarked build therefore self-reports a version that no release carries, which is how an official client came to insist on stopping an Omarchy host's server without saying why. HERDR_BUILD_CHANNEL and HERDR_BUILD_ID make it report 0.8.1-omarchy.<commit>. The channel is not "preview" because that gates is_preview(), which makes the stable updater treat every published release as installable and overwrite /usr/bin/herdr outside pacman.
pkgver() excludes the preview tags that sit on master between releases; describing without that returns a preview build id that pacman ranks below the version already shipped. It fails rather than falling back for the same reason: with no release tag reachable, every version it could invent sorts lower than what users already have.
Cross-architecture remote attach cannot bootstrap a helper for this build, because the stable manifest lists only published releases. That is true of any build from master, marked or not: an unmarked one asks the manifest for 0.8.1 and is told it does not exist.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.
Co-Authored-By: Codex XHigh <codex@openai.com>
The entry comes from upstream's AppImage rather than a hand-written one, because that is what carries the t3code:// scheme handlers, and it arrives named "T3 Code (Alpha)" from electron-builder's productName. That name is stale: the app's own code calls it `legacyUserDataDirName` and has already moved its data to ~/.config/t3code, so the launcher was advertising an identity upstream has moved off.
Rewrite Name= alongside the Exec= rewrite already there. The scheme handlers, the MimeType line and the rest of upstream's entry are untouched.
pkgrel goes to 2 because 0.0.33-1 is already published, and a packaging-only change reaches an installed machine only through a new release.
🤖 Generated by Opus 5 in Claude Code.
Pairing the app with the mise CLI does not work, and pinning the app back to
the tag behind PyPI's release does not rescue it: v2026.7.20's desktop reaches
"backend is ready" and then hangs without ever opening a window, against its
own matching runtime. Only the newest app against a runtime built from its own
commit starts, which is the arrangement upstream ships.
So the package returns to the newest tag and the launcher sets
HERMES_DESKTOP_IGNORE_EXISTING, which keeps the app off whatever hermes is on
PATH -- on Omarchy that is the mise CLI installed for the terminal agent, a
different release, and the version gap is what produced the 401. The app
provisions ~/.hermes itself on first launch instead.
mise and uv are no longer dependencies, since the launcher no longer installs
anything; git and curl are, because the app's own bootstrap needs them.
Built from v2026.8.18 against the CLI on PyPI, the app never starts: it
resolves the CLI, launches the backend, and then fails its readiness probe
with 401 Unauthorized. The two have to agree on the dashboard session-token
handshake -- the app scrapes window.__HERMES_SESSION_TOKEN__ out of the served
HTML, and web_server.py falls back to a random token when it cannot agree, so
every probe after that is rejected. A month separated the two: the tag was
2026-08-18, hermes-agent 0.19.0 on PyPI was 2026-07-20.
Upstream never meets this because their installer builds the app from the same
checkout it installs the CLI from. Pinning the CLI forward to the app's commit
is not open to us either -- Hermes refuses a non-editable install from a git
checkout and tells you to use `uv sync` instead. So the app is pinned back
instead, to the tag PyPI's current release was cut from.
Verified on a worker: the desktop reaches "Hermes backend is ready" and maps
its window, where the newer build stopped at the 401 every time.
The launcher only delegated to omarchy-install-hermes-cli and, when that was
absent, printed a warning to stderr -- which nothing sees under a graphical
launch -- and started the app anyway. The app then offered to install Hermes
itself, and that copy wins permanently: it checks ~/.hermes/hermes-agent
before it checks PATH, so installing the CLI afterwards changes nothing until
that directory is deleted.
So install it here when omarchy's script isn't around, with the same
interpreter pin and the same exported cooldown, and hand the app the real
executable by putting the mise install's bin directory on PATH instead of
leaving it to search. Its probe allows 15 seconds; resolving this way takes
0.2. mise and uv become dependencies, which is what makes the package
self-sufficient on a machine without Omarchy.
Pin the build stamp. write-build-stamp.mjs resolves the commit the app pins
its first-launch bootstrap to, preferring $GITHUB_SHA and otherwise running
`git rev-parse`. That fallback is wrong under makepkg: the build happens
inside this repository, so git ascends out of srcdir and stamps the app with
an omarchy-pkgs commit that means nothing upstream. Confirmed by rebuilding --
the stamp now reads the tag's own commit rather than this repo's HEAD.
Ship upstream's MIT licence. The package declares MIT and was installing only
Electron's licence text.
Repair a drifted CLI before launching rather than only a missing one. `mise
up` can leave Hermes rebuilt against the system Python, and a stub can be
there but cold; either way the wrapper's own repair takes minutes while the
app allows 15 seconds before bootstrapping its own copy. Running the installer
unconditionally costs nothing when Hermes is healthy.
Register the hermes:// scheme. The desktop entry took %U while declaring no
MimeType, so the scheme electron-builder configures went unhandled.
Three fixes from a review of the previous commit.
Chromium's sandbox helper ships setuid, the way Arch's own electron and chromium packages ship theirs. Dropping --no-sandbox was right, but it left the app relying on unprivileged user namespaces alone: on linux-hardened, or anywhere else they are denied, Electron falls back to the helper and aborts because it is not root-owned 4755.
The AppImage's usr/ tree is now read before it is removed. Deleting it wholesale is correct for what upstream ships today, and the version bumps arrive unattended, so a release that starts putting something needed in there would have had it dropped on the way past without anyone seeing it. Anything that is not a known icon or a known compatibility library stops the build instead.
The upstream hook checks that the feed still names the asset the PKGBUILD builds. It hashes whatever the feed points at, so a rename — or an arm64 build reaching the Linux feed first — would have pinned that file's checksum to a URL nobody fetches, and the failure would have surfaced a build later as a checksum mismatch.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.
Co-Authored-By: Codex XHigh <codex@openai.com>
Chromium falls back to XWayland often enough to matter, and the result is a
blurry window on every scaled display -- the same reason the ChatGPT and Grok
Bot launchers set this. Skipped when the caller has already named a platform.
Install > AI is for GUI apps, so the package becomes the desktop shell and the
terminal agent moves to a lazy mise stub that omarchy installs itself.
Nous builds Hermes Desktop for macOS and Windows only -- their download page
offers a .dmg and an .exe and points Linux users at a terminal install -- so
there is no vendor binary to repackage the way grok-bot repackages xAI's deb.
Their electron-builder config does carry a Linux target though, untested by
upstream but working: npm ci at the workspace root, npm run pack in
apps/desktop, and electron-builder stages node-pty and get-windows for
linux-x64 on its own. 337 MB unpacked, 129 MB packaged.
The app is only a shell -- it runs `hermes serve` against a CLI it does not
ship, and clones its own unpinned copy into ~/.hermes when it finds none. So
/usr/bin/hermes-desktop makes sure the CLI is there before handing over, and
says so plainly rather than bootstrapping a second Hermes where Omarchy's
installer isn't around to do it properly.
The AUR package installs the AppImage payload as it comes out of the image: AppRun, .DirIcon, the app's own desktop file and six compatibility libraries — libgconf, libappindicator, libindicator, libXss, libXtst, libnotify — bundled for distributions that do not ship them. Arch does, and nothing in the tree links the bundled copies anyway, so they were only along for the ride. The launcher's APPDIR, PATH, XDG_DATA_DIRS and GSETTINGS_SCHEMA_DIR exports existed to serve that layout, and CODEX_CLI_PATH is not a variable the app reads at all.
So the tree now goes to /usr/lib/t3code as a plain Electron install, next to how openai-codex-desktop ships. Cleaning that up meant rewriting most of package(), which is more than a patch should carry over an upstream we do not control, hence a local PKGBUILD and an .omarchy/upstream.sh that follows the electron-builder feed the app updates itself from.
Three things the AUR package lost that this keeps: the AppImage's own 16px-512px icons, rather than a separately downloaded 1024px PNG; upstream's desktop entry, which carries the t3code:// scheme handlers a hand-written one drops; and libnotify in depends, which Electron dlopens for notifications.
🤖 Generated by Opus 5 in Claude Code.
T3 Code is an open-source control plane for coding agents — Claude Code, Codex, OpenCode, Cursor and Grok driven from one desktop app, on the user's own subscriptions. Upstream ships a Linux x86_64 AppImage only, and the AUR's t3code-bin already tracks it, so this syncs from there in the fast ring.
One Omarchy patch: the AUR launcher runs Electron with --no-sandbox. Chromium falls back to a user-namespace sandbox when it finds no setuid helper, which is what happens on Arch, so the flag only turns the sandbox off. Verified both ways against the built package — sandboxed it reaches display setup, and only with namespaces restricted does it abort on the setuid helper.
🤖 Generated by Opus 5 in Claude Code.
Both files changed after the sums were generated -- the wrapper learned to
fall back to hermes when invoked under an unknown name, and the desktop entry
dropped its second main category -- so makepkg rejected them.
Hermes pins every one of its ~120 dependencies with == and declares
Requires-Python >=3.11,<3.14, so it can neither be built against Arch's
Python 3.14 nor share the python-* packages, and Arch's next Python bump
would break any venv this package built. So it ships a wrapper instead and
lets mise give Hermes a private environment -- the arrangement
omarchy-mise-install already makes for the other coding agents.
Two things the wrapper has to work around. mise reads [...] as its own tool
options rather than as Python extras, so the extras have to be spelled
[extras=all]; written [all] they are dropped without a word and the install
comes out byte-identical to a bare one. And given no compatible interpreter
to hand, uv builds the venv against the system Python in violation of
Hermes' own bound, reports success, and leaves the breakage to surface later
inside some dependency. The interpreter is therefore pinned on install and
re-checked on every run, since mise up reinstalls without the pin.
The icon is upstream's own 1024x1024 app icon -- the mark the site serves as
its favicon -- downscaled at build time so menus don't smudge it themselves.
A second review pass found the PKGBUILD rewriting could still go wrong in ways
the pattern matching did not anticipate: an array element carrying a ")" in a
comment left the tail of the old array behind, and jq's "$" also matches before
a trailing newline, so a pkgver of "1.0\n" passed validation and then broke sed
after the checksum arrays had already been written.
Rather than chase each shape, prove the result. Every edit now lands on a
scratch copy that is parsed with bash -n and read back to confirm it holds the
version and checksums we meant to write, and only then replaces the PKGBUILD in
a single rename. Corruption that slips past the matching fails loudly with the
original untouched instead of landing in a pull request.
The validation anchors are \A and \z accordingly, empty checksum lists are
rejected rather than written as '', and the hook picks the newest stanza with
vercmp so it agrees with the comparator the updater uses.
Also stop the launcher probing /.config when HOME and XDG_CONFIG_HOME are both
unset, and require a regular file, so a directory at that path is skipped
instead of crashing the app on startup.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OpenAI ships the ChatGPT desktop app several times a week and the AUR
packaging trails it -- as of this commit by a full version, 26.803.81509
against 26.810.52044. Every sync we took from there was a sync we could have
taken from OpenAI directly.
So track OpenAI's own Debian repository instead. Its per-architecture package
index carries the version and SHA256 of every deb, which makes an update two
small HTTP requests rather than a 750 MB download, and the pool keeps old
versions, so the URLs pinned here stay resolvable after the next release.
Omarchy now maintains the package outright: the max-zstd patch is simply part
of the PKGBUILD, chatgpt-launcher.sh is ours, and the Arch REUSE files are
gone -- they annotated packaging paths (.SRCINFO, keys/**, .nvchecker.toml)
that do not exist here. The app's own license still ships; package() installs
upstream's copyright file.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>