Skip to content

fix(runtimed): use in-bundle sidecar binary for launchd on macOS#1256

Merged
rgbkrk merged 3 commits intomainfrom
fix/macos-in-bundle-daemon
Mar 27, 2026
Merged

fix(runtimed): use in-bundle sidecar binary for launchd on macOS#1256
rgbkrk merged 3 commits intomainfrom
fix/macos-in-bundle-daemon

Conversation

@rgbkrk
Copy link
Copy Markdown
Member

@rgbkrk rgbkrk commented Mar 27, 2026

Summary

  • Point launchd plist at the binary inside the app bundle instead of copying it to ~/.local/share/runt/bin/runtimed. This eliminates the macOS "App Management" permission dialog, quarantine stripping, and code-signature crashes during upgrades.
  • Migrate existing installations automatically on next app launch (upgrade flow) or manually via runt daemon doctor --fix.
  • New runt doctor check (standalone_binary) detects legacy installs with [legacy] status and offers migration via --fix.
  • Preserve custom --binary installsinstall() and upgrade() detect in-bundle vs custom based on the source_binary argument, not the default config path. Custom binary paths are copied to the standalone location as before.
  • Doctor checks the plist binary path (what launchd actually runs) rather than default_binary_path() (what we'd prefer to install next), so legacy installs are diagnosed correctly even when an app bundle exists.

Context

The standalone copy approach was the initial design (PR #150), modeled after conventional Unix daemon patterns. PR #1056 later added atomic binary replacement to fix code-signature crashes during upgrades — but those crashes only existed because we were copying the binary out in the first place.

Test plan

  • runt daemon doctor shows [legacy] for existing standalone installs
  • runt daemon doctor --fix migrates plist to in-bundle, cleans up standalone binary
  • Fresh install (uninstall + launch app) creates plist pointing to in-bundle binary
  • App update triggers upgrade flow → no App Management prompt
  • runt daemon install --binary /custom/path copies to standalone location (not into app bundle)
  • Deleting the app → runt daemon doctor reports error appropriately
  • Linux/Windows paths unaffected (no behavioral change)

…andalone copy

The daemon was being copied from the nteract.app bundle to
~/.local/share/runt/bin/runtimed during installation. The launchd plist
pointed to this standalone copy, which triggered macOS's "App Management"
permission dialog and caused code-signature crashes during upgrades.

Now on macOS, the launchd plist points directly at the binary inside the
app bundle (e.g., /Applications/nteract.app/Contents/MacOS/runtimed).
This eliminates:
- The App Management permission prompt
- The quarantine-stripping hack
- The entire class of code-signature-invalidation bugs during upgrades

Existing installations are migrated automatically on the next app launch
(via the upgrade flow) or manually via `runt daemon doctor --fix`.

Changes:
- runt-workspace: Add migration helpers (legacy_standalone_binary_path,
  bundled_daemon_binary_path, plist_binary_path, is_legacy_standalone_install)
- service.rs: default_binary_path() prefers in-bundle path; install/upgrade
  skip binary copy for in-bundle; uninstall won't delete in-bundle binary;
  cleanup_legacy_binary() removes old standalone copy
- runt doctor: New standalone_binary check detects legacy installs;
  --fix migrates plist to in-bundle and cleans up standalone binary;
  quarantine check skipped for signed in-bundle binaries
@github-actions github-actions Bot added the daemon runtimed daemon, kernel management, sync server label Mar 27, 2026
rgbkrk added 2 commits March 27, 2026 14:58
Address two review issues:

P1: Doctor now reads the binary path from the installed launchd plist
(what launchd actually runs) rather than default_binary_path() (what
we'd prefer to install). This ensures a legacy standalone install gets
diagnosed correctly even when an app bundle exists on disk.

P2: install() and upgrade() now check the source_binary argument for
the in-bundle heuristic, not self.config.binary_path. When source_binary
is inside an app bundle, binary_path is set to it for the plist. When
a custom path is passed (e.g., --binary /path/to/runtimed), it is
honored and copied as before. Both methods now take &mut self.
default_binary_path() was resolving to the app-bundle binary for all
callers on macOS (including CLI tools like `runt`). This meant a custom
`runt daemon install --binary /path/to/runtimed` would try to copy into
the app bundle instead of the standalone install location.

Now default_binary_path() only returns the in-bundle path when
current_exe() is itself inside an app bundle (sidecar context). For
all other callers, it returns the standalone install path. The
install()/upgrade() methods handle in-bundle detection based on the
source_binary argument, not the default config path.
@rgbkrk rgbkrk marked this pull request as ready for review March 27, 2026 22:37
@rgbkrk rgbkrk merged commit fd8c230 into main Mar 27, 2026
25 of 27 checks passed
@rgbkrk rgbkrk deleted the fix/macos-in-bundle-daemon branch March 27, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

daemon runtimed daemon, kernel management, sync server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant