Problem to solve
anima install writes an ExecStart that points at the exact Ruby install the gem happened to be installed under. With a version manager, that path disappears the moment the Ruby minor rolls — and the brain goes into a permanent restart loop instead of failing visibly.
Observed on this machine after an Omarchy system upgrade bumped mise's Ruby:
anima.service: Unable to locate executable '/home/hoblin/.local/share/mise/installs/ruby/3.4.8/bin/anima': No such file or directory
anima.service: Failed at step EXEC spawning ...: No such file or directory
anima.service: Main process exited, code=exited, status=203/EXEC
anima.service: Scheduled restart job, restart counter is at 729.
729 failed starts, one every 5 seconds, silently flooding the journal. The brain had been down for hours before anyone noticed — it was found while diagnosing an unrelated crash.
What actually happened: the global mise config pins ruby = "3", so mise resolves the alias to whatever the latest 3.x is. The upgrade installed 3.4.10, repointed installs/ruby/3 at it, and pruned 3.4.8. The anima binstub lived under 3.4.8 and went with it — it now exists under no Ruby at all. The unit still points at the dead path.
Cause
lib/anima/installer.rb:150
anima_bin = File.join(Gem.bindir, "anima")
Under mise, Gem.bindir is ~/.local/share/mise/installs/ruby/<exact-version>/bin — a version-pinned absolute path baked into the unit at install time. Under rbenv/asdf/rvm it's the same story with a different prefix. Only a system Ruby (/usr/bin) is stable across upgrades.
Restart=on-failure with RestartSec=5 then makes it worse: systemd's default rate limit is 5 starts per 10s, and a 5s delay never trips it, so the unit retries forever rather than giving up and entering failed.
Solution
Resolve the executable through the version manager's shim rather than the concrete install path, and let the loop terminate.
ExecStart — prefer a stable indirection, in order:
- A shim on
PATH that survives version changes (~/.local/share/mise/shims/anima, ~/.rbenv/shims/anima) if one exists.
- Otherwise
mise exec ruby -- anima start -e production (or the detected manager's equivalent), so the manager resolves the current Ruby at start time.
- Otherwise fall back to today's
Gem.bindir path — correct for a system Ruby.
Rate limiting — add a start limit so a broken unit lands in failed where systemctl --user status shows it, instead of retrying until someone reads the journal:
[Unit]
StartLimitIntervalSec=300
StartLimitBurst=5
Detection on update — anima update already restarts the service; it should also notice that the recorded ExecStart no longer exists and rewrite the unit. That closes the loop for anyone whose unit was written before this fix.
Why not just re-run anima install?
It fixes one machine until the next minor bump, then breaks again. The bug is that the unit records a path with a lifetime shorter than the unit's own — re-running the installer re-creates that same fragility with a fresher version number.
Why a shim over mise exec?
The shim is one exec, no manager startup cost on every service start, and works even if mise itself moves. mise exec is the correct fallback because it's the only form guaranteed to work when shims are disabled.
Reproduction
- Install anima under a mise-managed Ruby pinned by major (
ruby = "3").
mise upgrade ruby (or any upgrade that rolls the resolved minor and prunes the old install).
systemctl --user status anima → 203/EXEC, restarting every 5s indefinitely.
Environment
- anima-core 1.5.1
- mise 2026.8.3 linux-x64
- Ruby: was 3.4.8 (pruned), now 3.4.10; global pin
ruby = "3"
- Arch Linux / Omarchy, systemd user service
[claude-opus-5[1m]] on behalf of hoblin
Problem to solve
anima installwrites anExecStartthat points at the exact Ruby install the gem happened to be installed under. With a version manager, that path disappears the moment the Ruby minor rolls — and the brain goes into a permanent restart loop instead of failing visibly.Observed on this machine after an Omarchy system upgrade bumped mise's Ruby:
729 failed starts, one every 5 seconds, silently flooding the journal. The brain had been down for hours before anyone noticed — it was found while diagnosing an unrelated crash.
What actually happened: the global mise config pins
ruby = "3", so mise resolves the alias to whatever the latest 3.x is. The upgrade installed 3.4.10, repointedinstalls/ruby/3at it, and pruned 3.4.8. Theanimabinstub lived under 3.4.8 and went with it — it now exists under no Ruby at all. The unit still points at the dead path.Cause
lib/anima/installer.rb:150Under mise,
Gem.bindiris~/.local/share/mise/installs/ruby/<exact-version>/bin— a version-pinned absolute path baked into the unit at install time. Under rbenv/asdf/rvm it's the same story with a different prefix. Only a system Ruby (/usr/bin) is stable across upgrades.Restart=on-failurewithRestartSec=5then makes it worse: systemd's default rate limit is 5 starts per 10s, and a 5s delay never trips it, so the unit retries forever rather than giving up and enteringfailed.Solution
Resolve the executable through the version manager's shim rather than the concrete install path, and let the loop terminate.
ExecStart — prefer a stable indirection, in order:
PATHthat survives version changes (~/.local/share/mise/shims/anima,~/.rbenv/shims/anima) if one exists.mise exec ruby -- anima start -e production(or the detected manager's equivalent), so the manager resolves the current Ruby at start time.Gem.bindirpath — correct for a system Ruby.Rate limiting — add a start limit so a broken unit lands in
failedwheresystemctl --user statusshows it, instead of retrying until someone reads the journal:Detection on update —
anima updatealready restarts the service; it should also notice that the recordedExecStartno longer exists and rewrite the unit. That closes the loop for anyone whose unit was written before this fix.Why not just re-run
anima install?It fixes one machine until the next minor bump, then breaks again. The bug is that the unit records a path with a lifetime shorter than the unit's own — re-running the installer re-creates that same fragility with a fresher version number.
Why a shim over
mise exec?The shim is one exec, no manager startup cost on every service start, and works even if
miseitself moves.mise execis the correct fallback because it's the only form guaranteed to work when shims are disabled.Reproduction
ruby = "3").mise upgrade ruby(or any upgrade that rolls the resolved minor and prunes the old install).systemctl --user status anima→203/EXEC, restarting every 5s indefinitely.Environment
ruby = "3"[claude-opus-5[1m]] on behalf of hoblin