Skip to content

Installer pins systemd ExecStart to a version-specific Ruby path #496

Description

@hoblin

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:

  1. A shim on PATH that survives version changes (~/.local/share/mise/shims/anima, ~/.rbenv/shims/anima) if one exists.
  2. Otherwise mise exec ruby -- anima start -e production (or the detected manager's equivalent), so the manager resolves the current Ruby at start time.
  3. 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 updateanima 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

  1. Install anima under a mise-managed Ruby pinned by major (ruby = "3").
  2. mise upgrade ruby (or any upgrade that rolls the resolved minor and prunes the old install).
  3. systemctl --user status anima203/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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions