Children re-execute a private copy of the startup binary (safe in-place placement)

Merged
AF
2d ago
+800-18

AFT re-executes its own binary for several things: sandboxed shell launches (aft sandbox-launch), the gh shim on a child's PATH, the detached login-shell PATH refresh, and the performance profile helper. Each used the deployment pathname. Placing a new build without a restart (--no-restart) then left a running old-build daemon launching new-build helpers, whose protocols may differ.

Now, at startup, AFT pins the image it's running:

  • macOS: it keeps the startup executable open and hard-links (or, failing that, byte-copies) it into a private per-process directory, <storage>/run/aft-pinned-<pid>-<start-ms>/aft.
  • Linux: it does the same from /proc/self/exe, with linkat(AT_SYMLINK_FOLLOW) or an exact copy. A Landlock-confined shell can't open the daemon's procfs exe link, so a plain file is needed.

All re-exec sites use that path. Pins of dead processes (PID plus start-time check) are removed at the next startup. Windows keeps its executable path, because Windows doesn't let placement replace a running .exe. An explicit gh_shim.binary_path setting still wins. Sandbox profiles are unchanged.

Tests: executable_replacement_test::pinned_executable_real_server_survives_binary_replacement_and_cleans_dead_pins replaces the binary under a live server, checks the sandboxed launch and gh still use the original image, and checks dead pins are cleaned. It passed natively on macOS. On Linux it needs Landlock: MOTOR's current VMs lack it (kernel 6.18.58 returns ENOSYS, a fix is in their next image), and GitHub's Ubuntu runners have it, so the Linux run is this PR's CI integration shards. The test refuses to run without Landlock rather than skipping confinement.


Summary by cubic

Fixes AFT's child-process re-execution so a running daemon keeps using its original binary image even when a no-restart deployment replaces the deployed executable. Adds a search descriptor confirming no ranking or indexing code changes.

  • AFT pins its startup executable into a per-process directory under <storage>/run on macOS and Linux (via hard link or byte copy), used by sandbox-launch, the gh shim, detached PATH refresh, and the perf-sweep helper. Only --subc daemons pin; standalone commands fall back to current_exe and the shared storage/shims PATH entry, so a fresh install creates no storage.
  • The gh shim directory is per-process, so a second daemon running a different build cannot retarget an existing server's PATH entry.
  • Pins whose PID and start time no longer match a live process are cleaned up on the next startup; an explicit gh_shim.binary_path setting still takes precedence.
  • Windows behavior is unchanged because its file locking already prevents replacing a running .exe.
  • Running pins are excluded from forensic archives.
  • Adds integration coverage that replaces the binary under a live server, verifies sandboxed shells and gh still run the original image, and checks dead pins are cleaned; the Linux scenario requires native Landlock support.